fix(agents): clear stale frontier tool denies
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -8,7 +8,7 @@ import { applyEnvironmentContext } from "./environment-context"
|
|||||||
import { applyCategoryOverride, mergeAgentConfig } from "./agent-overrides"
|
import { applyCategoryOverride, mergeAgentConfig } from "./agent-overrides"
|
||||||
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
|
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
|
||||||
import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard"
|
import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard"
|
||||||
import { getFrontierToolSchemaPermission } from "../frontier-tool-schema-guard"
|
import { applyFrontierToolSchemaPermission } from "../frontier-tool-schema-guard"
|
||||||
|
|
||||||
export function maybeCreateHephaestusConfig(input: {
|
export function maybeCreateHephaestusConfig(input: {
|
||||||
disabledAgents: string[]
|
disabledAgents: string[]
|
||||||
@@ -90,10 +90,11 @@ export function maybeCreateHephaestusConfig(input: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const resolvedModel = hephaestusConfig.model ?? ""
|
const resolvedModel = hephaestusConfig.model ?? ""
|
||||||
const frontierDeny = getFrontierToolSchemaPermission(resolvedModel)
|
hephaestusConfig.permission = applyFrontierToolSchemaPermission(
|
||||||
if (Object.keys(frontierDeny).length > 0 && hephaestusConfig.permission) {
|
hephaestusConfig.permission,
|
||||||
Object.assign(hephaestusConfig.permission, frontierDeny)
|
resolvedModel,
|
||||||
}
|
hephaestusOverride?.permission
|
||||||
|
)
|
||||||
|
|
||||||
const gptDeny = getGptApplyPatchPermission(resolvedModel)
|
const gptDeny = getGptApplyPatchPermission(resolvedModel)
|
||||||
if (Object.keys(gptDeny).length > 0 && hephaestusConfig.permission) {
|
if (Object.keys(gptDeny).length > 0 && hephaestusConfig.permission) {
|
||||||
|
|||||||
@@ -143,6 +143,75 @@ describe("maybeCreateSisyphusConfig", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#given frontier default model with category override to non-frontier model", () => {
|
||||||
|
test("#when config is created #then stale grep and glob denies are cleared", () => {
|
||||||
|
// given
|
||||||
|
const agentOverrides: AgentOverrides = {
|
||||||
|
sisyphus: {
|
||||||
|
category: "non-frontier",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mergedCategories: Record<string, CategoryConfig> = {
|
||||||
|
"non-frontier": {
|
||||||
|
model: "openai/gpt-5.4",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
const config = maybeCreateSisyphusConfig({
|
||||||
|
disabledAgents: [],
|
||||||
|
agentOverrides,
|
||||||
|
availableModels: new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"]),
|
||||||
|
systemDefaultModel: "anthropic/claude-opus-4-7",
|
||||||
|
isFirstRunNoCache: false,
|
||||||
|
availableAgents: [],
|
||||||
|
availableSkills: [],
|
||||||
|
availableCategories: [],
|
||||||
|
mergedCategories,
|
||||||
|
useTaskSystem: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(config?.model).toBe("openai/gpt-5.4");
|
||||||
|
expect(config?.permission).not.toHaveProperty("grep");
|
||||||
|
expect(config?.permission).not.toHaveProperty("glob");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#given non-frontier model with user override denying grep and glob", () => {
|
||||||
|
test("#when config is created #then explicit user denies are preserved", () => {
|
||||||
|
// given
|
||||||
|
const agentOverrides: AgentOverrides = {
|
||||||
|
sisyphus: {
|
||||||
|
model: "openai/gpt-5.4",
|
||||||
|
permission: {
|
||||||
|
grep: "deny",
|
||||||
|
glob: "deny",
|
||||||
|
} as Record<string, "deny">,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mergedCategories: Record<string, CategoryConfig> = {};
|
||||||
|
|
||||||
|
// when
|
||||||
|
const config = maybeCreateSisyphusConfig({
|
||||||
|
disabledAgents: [],
|
||||||
|
agentOverrides,
|
||||||
|
availableModels: new Set(["openai/gpt-5.4"]),
|
||||||
|
systemDefaultModel: "openai/gpt-5.4",
|
||||||
|
isFirstRunNoCache: false,
|
||||||
|
availableAgents: [],
|
||||||
|
availableSkills: [],
|
||||||
|
availableCategories: [],
|
||||||
|
mergedCategories,
|
||||||
|
useTaskSystem: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(config?.permission).toHaveProperty("grep", "deny");
|
||||||
|
expect(config?.permission).toHaveProperty("glob", "deny");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("#given generic GPT model with user override allowing apply_patch", () => {
|
describe("#given generic GPT model with user override allowing apply_patch", () => {
|
||||||
test("#when config is created #then apply_patch is still denied", () => {
|
test("#when config is created #then apply_patch is still denied", () => {
|
||||||
// given
|
// given
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { applyOverrides } from "./agent-overrides"
|
|||||||
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
|
import { applyModelResolution, getFirstFallbackModel } from "./model-resolution"
|
||||||
import { createSisyphusAgent } from "../sisyphus"
|
import { createSisyphusAgent } from "../sisyphus"
|
||||||
import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard"
|
import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard"
|
||||||
import { getFrontierToolSchemaPermission } from "../frontier-tool-schema-guard"
|
import { applyFrontierToolSchemaPermission } from "../frontier-tool-schema-guard"
|
||||||
|
|
||||||
export function maybeCreateSisyphusConfig(input: {
|
export function maybeCreateSisyphusConfig(input: {
|
||||||
disabledAgents: string[]
|
disabledAgents: string[]
|
||||||
@@ -84,10 +84,11 @@ export function maybeCreateSisyphusConfig(input: {
|
|||||||
sisyphusConfig = applyOverrides(sisyphusConfig, sisyphusOverride, mergedCategories, directory)
|
sisyphusConfig = applyOverrides(sisyphusConfig, sisyphusOverride, mergedCategories, directory)
|
||||||
|
|
||||||
const resolvedModel = sisyphusConfig.model ?? ""
|
const resolvedModel = sisyphusConfig.model ?? ""
|
||||||
const frontierDeny = getFrontierToolSchemaPermission(resolvedModel)
|
sisyphusConfig.permission = applyFrontierToolSchemaPermission(
|
||||||
if (Object.keys(frontierDeny).length > 0 && sisyphusConfig.permission) {
|
sisyphusConfig.permission,
|
||||||
Object.assign(sisyphusConfig.permission, frontierDeny)
|
resolvedModel,
|
||||||
}
|
sisyphusOverride?.permission
|
||||||
|
)
|
||||||
|
|
||||||
const gptDeny = getGptApplyPatchPermission(resolvedModel)
|
const gptDeny = getGptApplyPatchPermission(resolvedModel)
|
||||||
if (Object.keys(gptDeny).length > 0 && sisyphusConfig.permission) {
|
if (Object.keys(gptDeny).length > 0 && sisyphusConfig.permission) {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
import type { AgentConfig } from "@opencode-ai/sdk"
|
||||||
import { isGpt5_5Model } from "./types"
|
import { isGpt5_5Model } from "./types"
|
||||||
|
import type { PermissionValue } from "../shared/permission-compat"
|
||||||
|
|
||||||
|
const FRONTIER_TOOL_SCHEMA_NAMES = ["grep", "glob"] as const
|
||||||
|
type MutablePermission = Record<string, PermissionValue | Record<string, PermissionValue>>
|
||||||
|
|
||||||
function isOpus47Model(model: string): boolean {
|
function isOpus47Model(model: string): boolean {
|
||||||
const modelName = model.includes("/") ? (model.split("/").pop() ?? model) : model
|
const modelName = model.includes("/") ? (model.split("/").pop() ?? model) : model
|
||||||
@@ -10,3 +15,25 @@ export function getFrontierToolSchemaPermission(model: string): Record<string, "
|
|||||||
? { grep: "deny" as const, glob: "deny" as const }
|
? { grep: "deny" as const, glob: "deny" as const }
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyFrontierToolSchemaPermission(
|
||||||
|
permission: AgentConfig["permission"] | undefined,
|
||||||
|
model: string,
|
||||||
|
explicitPermission?: AgentConfig["permission"]
|
||||||
|
): AgentConfig["permission"] | undefined {
|
||||||
|
if (!permission) return permission
|
||||||
|
|
||||||
|
const nextPermission: MutablePermission = { ...permission }
|
||||||
|
const explicitPermissionMap = explicitPermission as MutablePermission | undefined
|
||||||
|
const frontierDeny = getFrontierToolSchemaPermission(model)
|
||||||
|
if (Object.keys(frontierDeny).length > 0) {
|
||||||
|
Object.assign(nextPermission, frontierDeny)
|
||||||
|
return nextPermission as AgentConfig["permission"]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const toolName of FRONTIER_TOOL_SCHEMA_NAMES) {
|
||||||
|
if (explicitPermissionMap?.[toolName] === "deny") continue
|
||||||
|
delete nextPermission[toolName]
|
||||||
|
}
|
||||||
|
return nextPermission as AgentConfig["permission"]
|
||||||
|
}
|
||||||
|
|||||||
@@ -484,4 +484,73 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
|
|||||||
expect(config?.permission).toHaveProperty("glob", "deny");
|
expect(config?.permission).toHaveProperty("glob", "deny");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("#given frontier default model with category override to non-frontier model", () => {
|
||||||
|
test("#when config is created #then stale grep and glob denies are cleared", () => {
|
||||||
|
// given
|
||||||
|
const agentOverrides: AgentOverrides = {
|
||||||
|
hephaestus: {
|
||||||
|
category: "non-frontier",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mergedCategories: Record<string, CategoryConfig> = {
|
||||||
|
"non-frontier": {
|
||||||
|
model: "openai/gpt-5.4",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// when
|
||||||
|
const config = maybeCreateHephaestusConfig({
|
||||||
|
disabledAgents: [],
|
||||||
|
agentOverrides,
|
||||||
|
availableModels: new Set(["openai/gpt-5.5", "openai/gpt-5.4"]),
|
||||||
|
systemDefaultModel: "openai/gpt-5.5",
|
||||||
|
isFirstRunNoCache: false,
|
||||||
|
availableAgents: [],
|
||||||
|
availableSkills: [],
|
||||||
|
availableCategories: [],
|
||||||
|
mergedCategories,
|
||||||
|
useTaskSystem: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(config?.model).toBe("openai/gpt-5.4");
|
||||||
|
expect(config?.permission).not.toHaveProperty("grep");
|
||||||
|
expect(config?.permission).not.toHaveProperty("glob");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("#given non-frontier model with user override denying grep and glob", () => {
|
||||||
|
test("#when config is created #then explicit user denies are preserved", () => {
|
||||||
|
// given
|
||||||
|
const agentOverrides: AgentOverrides = {
|
||||||
|
hephaestus: {
|
||||||
|
model: "openai/gpt-5.4",
|
||||||
|
permission: {
|
||||||
|
grep: "deny",
|
||||||
|
glob: "deny",
|
||||||
|
} as Record<string, "deny">,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mergedCategories: Record<string, CategoryConfig> = {};
|
||||||
|
|
||||||
|
// when
|
||||||
|
const config = maybeCreateHephaestusConfig({
|
||||||
|
disabledAgents: [],
|
||||||
|
agentOverrides,
|
||||||
|
availableModels: new Set(["openai/gpt-5.4"]),
|
||||||
|
systemDefaultModel: "openai/gpt-5.4",
|
||||||
|
isFirstRunNoCache: false,
|
||||||
|
availableAgents: [],
|
||||||
|
availableSkills: [],
|
||||||
|
availableCategories: [],
|
||||||
|
mergedCategories,
|
||||||
|
useTaskSystem: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(config?.permission).toHaveProperty("grep", "deny");
|
||||||
|
expect(config?.permission).toHaveProperty("glob", "deny");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user