diff --git a/packages/server/src/server/agent/provider-snapshot-manager.test.ts b/packages/server/src/server/agent/provider-snapshot-manager.test.ts index c48479647..b28accd07 100644 --- a/packages/server/src/server/agent/provider-snapshot-manager.test.ts +++ b/packages/server/src/server/agent/provider-snapshot-manager.test.ts @@ -12,6 +12,7 @@ import type { } from "./agent-sdk-types.js"; import type { ManagedAgent } from "./agent-manager.js"; import { ProviderSnapshotManager } from "./provider-snapshot-manager.js"; +import { OpenCodeAgentClient } from "./providers/opencode-agent.js"; const TEST_CAPABILITIES = { supportsStreaming: false, @@ -449,6 +450,62 @@ describe("ProviderSnapshotManager public surface", () => { manager.destroy(); } }); + + test("treats an OpenCode parent with auto accept as unattended when resolving an explicit child mode", async () => { + const openCode = new OpenCodeAgentClient(createTestLogger()); + const modes: AgentMode[] = [ + { id: "build", label: "Build" }, + { id: "base", label: "Base" }, + { id: "orchestrator", label: "Orchestrator" }, + ]; + const manager = new ProviderSnapshotManager({ + logger: createTestLogger(), + providerOverrides: { + claude: { enabled: false }, + codex: { enabled: false }, + copilot: { enabled: false }, + pi: { enabled: false }, + }, + extraClients: { + opencode: createExtraClient("opencode", { + async isAvailable() { + return true; + }, + async listModes() { + return modes; + }, + resolveCreateConfig: openCode.resolveCreateConfig.bind(openCode), + isCreateConfigUnattended: openCode.isCreateConfigUnattended.bind(openCode), + }), + }, + }); + try { + const parent = { + id: "parent-agent", + provider: "opencode", + currentModeId: "orchestrator", + availableModes: modes, + config: { + provider: "opencode", + cwd: "/tmp/project", + featureValues: { auto_accept: true }, + }, + } as ManagedAgent; + + const resolved = await manager.resolveCreateConfig({ + cwd: "/tmp/project", + provider: "opencode", + requestedMode: "base", + featureValues: undefined, + parent, + unattended: false, + }); + + expect(resolved).toEqual({ modeId: "base", featureValues: { auto_accept: true } }); + } finally { + manager.destroy(); + } + }); }); describe("ProviderSnapshotManager applyMutableProviderConfig", () => { diff --git a/packages/server/src/server/agent/providers/opencode-agent.full-access.test.ts b/packages/server/src/server/agent/providers/opencode-agent.full-access.test.ts index 1e6442092..7ead93eb1 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.full-access.test.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.full-access.test.ts @@ -206,6 +206,29 @@ describe("OpenCode auto_accept feature", () => { ).toEqual({ modeId: "paseo-custom", featureValues: { auto_accept: true } }); }); + test("inherits auto accept from an OpenCode parent when the child chooses a mode", () => { + const client = new OpenCodeAgentClient(createTestLogger()); + + expect( + client.resolveCreateConfig({ + provider: "opencode", + requestedMode: "base", + featureValues: undefined, + parent: { + provider: "opencode", + modeId: "orchestrator", + isUnattended: true, + }, + unattended: true, + availableModes: [ + { id: "build", label: "Build" }, + { id: "base", label: "Base" }, + { id: "orchestrator", label: "Orchestrator" }, + ], + }), + ).toEqual({ modeId: "base", featureValues: { auto_accept: true } }); + }); + test("auto-approves tool permissions when auto accept is enabled", async () => { const { openCodeClient, runtime } = mockOpenCodeClient({ events: [toolPermissionEvent(), idleEvent()], diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index fd301e991..977b4a387 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -133,8 +133,8 @@ function resolveOpenCodeCreateConfig( ): ResolveAgentCreateConfigResult { const legacyFullAccess = input.requestedMode === OPENCODE_LEGACY_FULL_ACCESS_MODE_ID; const parent = input.parent; - const inheritsUnattended = - input.requestedMode === undefined && (input.unattended || parent?.isUnattended === true); + const isUnattendedCreate = input.unattended || parent?.isUnattended === true; + const inheritsUnattended = input.requestedMode === undefined && isUnattendedCreate; const inheritedOpenCodeMode = inheritsUnattended && parent?.provider === input.provider ? (parent.modeId ?? undefined) @@ -144,7 +144,7 @@ function resolveOpenCodeCreateConfig( : (input.requestedMode ?? inheritedOpenCodeMode); const featureValues = legacyFullAccess || - (inheritsUnattended && input.featureValues?.[OPENCODE_AUTO_ACCEPT_FEATURE_ID] === undefined) + (isUnattendedCreate && input.featureValues?.[OPENCODE_AUTO_ACCEPT_FEATURE_ID] === undefined) ? withOpenCodeAutoAcceptFeature(input.featureValues, true) : input.featureValues;