From 10371d5bdeffa33c0b1b095e9cbdbdf3c0a536a4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 10 Apr 2026 18:47:17 +0900 Subject: [PATCH] fix(agents): re-apply GPT apply_patch deny after config override merge --- src/agents/builtin-agents/hephaestus-agent.ts | 8 ++ .../builtin-agents/sisyphus-agent.test.ts | 109 ++++++++++++++++++ src/agents/builtin-agents/sisyphus-agent.ts | 8 ++ src/agents/hephaestus/agent.test.ts | 108 +++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 src/agents/builtin-agents/sisyphus-agent.test.ts diff --git a/src/agents/builtin-agents/hephaestus-agent.ts b/src/agents/builtin-agents/hephaestus-agent.ts index a4f0a801d..a32064c63 100644 --- a/src/agents/builtin-agents/hephaestus-agent.ts +++ b/src/agents/builtin-agents/hephaestus-agent.ts @@ -7,6 +7,7 @@ import { createHephaestusAgent } from "../hephaestus" import { applyEnvironmentContext } from "./environment-context" import { applyCategoryOverride, mergeAgentConfig } from "./agent-overrides" import { applyModelResolution, getFirstFallbackModel } from "./model-resolution" +import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard" export function maybeCreateHephaestusConfig(input: { disabledAgents: string[] @@ -86,5 +87,12 @@ export function maybeCreateHephaestusConfig(input: { if (hephaestusOverride) { hephaestusConfig = mergeAgentConfig(hephaestusConfig, hephaestusOverride, directory) } + + const resolvedModel = hephaestusConfig.model ?? "" + const gptDeny = getGptApplyPatchPermission(resolvedModel) + if (Object.keys(gptDeny).length > 0 && hephaestusConfig.permission) { + Object.assign(hephaestusConfig.permission, gptDeny) + } + return hephaestusConfig } diff --git a/src/agents/builtin-agents/sisyphus-agent.test.ts b/src/agents/builtin-agents/sisyphus-agent.test.ts new file mode 100644 index 000000000..e55fea125 --- /dev/null +++ b/src/agents/builtin-agents/sisyphus-agent.test.ts @@ -0,0 +1,109 @@ +import { describe, expect, test } from "bun:test"; +import { maybeCreateSisyphusConfig } from "./sisyphus-agent"; +import type { AgentOverrides } from "../types"; +import type { CategoryConfig } from "../../config/schema"; + +describe("maybeCreateSisyphusConfig", () => { + describe("#given GPT model with user override allowing apply_patch", () => { + test("#when config is created #then apply_patch is still denied", () => { + // given + const agentOverrides: AgentOverrides = { + sisyphus: { + model: "openai/gpt-5.4", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // 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).toBeDefined(); + expect(config?.model).toBe("openai/gpt-5.4"); + expect(config?.permission).toHaveProperty("apply_patch", "deny"); + }); + }); + + describe("#given non-GPT model with user override", () => { + test("#when config is created #then apply_patch is not forced to deny", () => { + // given + const agentOverrides: AgentOverrides = { + sisyphus: { + model: "anthropic/claude-opus-4-6", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // when + const config = maybeCreateSisyphusConfig({ + disabledAgents: [], + agentOverrides, + availableModels: new Set(["anthropic/claude-opus-4-6"]), + systemDefaultModel: "anthropic/claude-opus-4-6", + isFirstRunNoCache: false, + availableAgents: [], + availableSkills: [], + availableCategories: [], + mergedCategories, + useTaskSystem: false, + }); + + // then + expect(config).toBeDefined(); + expect(config?.model).toBe("anthropic/claude-opus-4-6"); + // Claude models should allow the user override + expect(config?.permission).toHaveProperty("apply_patch", "allow"); + }); + }); + + describe("#given generic GPT model with user override allowing apply_patch", () => { + test("#when config is created #then apply_patch is still denied", () => { + // given + const agentOverrides: AgentOverrides = { + sisyphus: { + model: "openai/gpt-4o", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // when + const config = maybeCreateSisyphusConfig({ + disabledAgents: [], + agentOverrides, + availableModels: new Set(["openai/gpt-4o"]), + systemDefaultModel: "openai/gpt-4o", + isFirstRunNoCache: false, + availableAgents: [], + availableSkills: [], + availableCategories: [], + mergedCategories, + useTaskSystem: false, + }); + + // then + expect(config).toBeDefined(); + expect(config?.model).toBe("openai/gpt-4o"); + expect(config?.permission).toHaveProperty("apply_patch", "deny"); + }); + }); +}); diff --git a/src/agents/builtin-agents/sisyphus-agent.ts b/src/agents/builtin-agents/sisyphus-agent.ts index d326f9a6a..97aef5f61 100644 --- a/src/agents/builtin-agents/sisyphus-agent.ts +++ b/src/agents/builtin-agents/sisyphus-agent.ts @@ -7,6 +7,7 @@ import { applyEnvironmentContext } from "./environment-context" import { applyOverrides } from "./agent-overrides" import { applyModelResolution, getFirstFallbackModel } from "./model-resolution" import { createSisyphusAgent } from "../sisyphus" +import { getGptApplyPatchPermission } from "../gpt-apply-patch-guard" export function maybeCreateSisyphusConfig(input: { disabledAgents: string[] @@ -80,6 +81,13 @@ export function maybeCreateSisyphusConfig(input: { } sisyphusConfig = applyOverrides(sisyphusConfig, sisyphusOverride, mergedCategories, directory) + + const resolvedModel = sisyphusConfig.model ?? "" + const gptDeny = getGptApplyPatchPermission(resolvedModel) + if (Object.keys(gptDeny).length > 0 && sisyphusConfig.permission) { + Object.assign(sisyphusConfig.permission, gptDeny) + } + sisyphusConfig = applyEnvironmentContext(sisyphusConfig, directory, { disableOmoEnv, }) diff --git a/src/agents/hephaestus/agent.test.ts b/src/agents/hephaestus/agent.test.ts index 0885e7839..7818e0361 100644 --- a/src/agents/hephaestus/agent.test.ts +++ b/src/agents/hephaestus/agent.test.ts @@ -277,3 +277,111 @@ describe("createHephaestusAgent", () => { expect(config.prompt).not.toContain("task_create"); }); }); + +import { maybeCreateHephaestusConfig } from "../builtin-agents/hephaestus-agent"; +import type { AgentOverrides } from "../types"; +import type { CategoryConfig } from "../../config/schema"; + +describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => { + describe("#given GPT model with user override allowing apply_patch", () => { + test("#when config is created #then apply_patch is still denied", () => { + // given + const agentOverrides: AgentOverrides = { + hephaestus: { + model: "openai/gpt-5.4", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // 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).toBeDefined(); + expect(config?.model).toBe("openai/gpt-5.4"); + expect(config?.permission).toHaveProperty("apply_patch", "deny"); + }); + }); + + describe("#given non-GPT model with user override allowing apply_patch", () => { + test("#when config is created #then user override is respected", () => { + // given + const agentOverrides: AgentOverrides = { + hephaestus: { + model: "anthropic/claude-opus-4-6", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // when + const config = maybeCreateHephaestusConfig({ + disabledAgents: [], + agentOverrides, + availableModels: new Set(["anthropic/claude-opus-4-6"]), + systemDefaultModel: "anthropic/claude-opus-4-6", + isFirstRunNoCache: false, + availableAgents: [], + availableSkills: [], + availableCategories: [], + mergedCategories, + useTaskSystem: false, + }); + + // then + expect(config).toBeDefined(); + expect(config?.model).toBe("anthropic/claude-opus-4-6"); + expect(config?.permission).toHaveProperty("apply_patch", "allow"); + }); + }); + + describe("#given generic GPT model with user override allowing apply_patch", () => { + test("#when config is created #then apply_patch is still denied", () => { + // given + const agentOverrides: AgentOverrides = { + hephaestus: { + model: "openai/gpt-4o", + permission: { + apply_patch: "allow", + }, + }, + }; + const mergedCategories: Record = {}; + + // when + const config = maybeCreateHephaestusConfig({ + disabledAgents: [], + agentOverrides, + availableModels: new Set(["openai/gpt-4o"]), + systemDefaultModel: "openai/gpt-4o", + isFirstRunNoCache: false, + availableAgents: [], + availableSkills: [], + availableCategories: [], + mergedCategories, + useTaskSystem: false, + }); + + // then + expect(config).toBeDefined(); + expect(config?.model).toBe("openai/gpt-4o"); + expect(config?.permission).toHaveProperty("apply_patch", "deny"); + }); + }); +});