fix(background-agent): honor explicit model override in manager

Keep BackgroundManager launch and resume from sending both agent and model so OpenCode does not override configured subagent models. Add launch and resume regressions for the live production path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
YeonGyu-Kim
2026-03-24 20:28:01 +09:00
parent cea8769a7f
commit 3b41191980
2 changed files with 79 additions and 8 deletions

View File

@@ -1668,7 +1668,7 @@ describe("BackgroundManager.resume model persistence", () => {
// then - model should be passed in prompt body
expect(promptCalls).toHaveLength(1)
expect(promptCalls[0].body.model).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" })
expect(promptCalls[0].body.agent).toBe("explore")
expect("agent" in promptCalls[0].body).toBe(false)
})
test("should NOT pass model when task has no model (backward compatibility)", async () => {
@@ -1832,6 +1832,73 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
expect(task2.status).toBe("pending")
})
test("should omit agent when launch has model and keep agent without model", async () => {
// given
const promptBodies: Array<Record<string, unknown>> = []
let resolveFirstPromptStarted: (() => void) | undefined
let resolveSecondPromptStarted: (() => void) | undefined
const firstPromptStarted = new Promise<void>((resolve) => {
resolveFirstPromptStarted = resolve
})
const secondPromptStarted = new Promise<void>((resolve) => {
resolveSecondPromptStarted = resolve
})
const customClient = {
session: {
create: async (_args?: unknown) => ({ data: { id: `ses_${crypto.randomUUID()}` } }),
get: async () => ({ data: { directory: "/test/dir" } }),
prompt: async () => ({}),
promptAsync: async (args: { path: { id: string }; body: Record<string, unknown> }) => {
promptBodies.push(args.body)
if (promptBodies.length === 1) {
resolveFirstPromptStarted?.()
}
if (promptBodies.length === 2) {
resolveSecondPromptStarted?.()
}
return {}
},
messages: async () => ({ data: [] }),
todo: async () => ({ data: [] }),
status: async () => ({ data: {} }),
abort: async () => ({}),
},
}
manager.shutdown()
manager = new BackgroundManager({ client: customClient, directory: tmpdir() } as unknown as PluginInput)
const launchInputWithModel = {
description: "Test task with model",
prompt: "Do something",
agent: "test-agent",
parentSessionID: "parent-session",
parentMessageID: "parent-message",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" },
}
const launchInputWithoutModel = {
description: "Test task without model",
prompt: "Do something else",
agent: "test-agent",
parentSessionID: "parent-session",
parentMessageID: "parent-message",
}
// when
const taskWithModel = await manager.launch(launchInputWithModel)
await firstPromptStarted
const taskWithoutModel = await manager.launch(launchInputWithoutModel)
await secondPromptStarted
// then
expect(taskWithModel.status).toBe("pending")
expect(taskWithoutModel.status).toBe("pending")
expect(promptBodies).toHaveLength(2)
expect(promptBodies[0].model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" })
expect("agent" in promptBodies[0]).toBe(false)
expect(promptBodies[1].agent).toBe("test-agent")
expect("model" in promptBodies[1]).toBe(false)
})
test("should queue multiple tasks without blocking", async () => {
// given
const config = { defaultConcurrency: 2 }

View File

@@ -515,7 +515,9 @@ export class BackgroundManager {
promptWithModelSuggestionRetry(this.client, {
path: { id: sessionID },
body: {
agent: input.agent,
// When a model is explicitly provided, omit the agent name so opencode's
// built-in agent fallback chain does not override the user-specified model.
...(launchModel ? {} : { agent: input.agent }),
...(launchModel ? { model: launchModel } : {}),
...(launchVariant ? { variant: launchVariant } : {}),
system: input.skillContent,
@@ -792,7 +794,9 @@ export class BackgroundManager {
this.client.session.promptAsync({
path: { id: existingTask.sessionID },
body: {
agent: existingTask.agent,
// When a model is explicitly provided, omit the agent name so opencode's
// built-in agent fallback chain does not override the user-specified model.
...(resumeModel ? {} : { agent: existingTask.agent }),
...(resumeModel ? { model: resumeModel } : {}),
...(resumeVariant ? { variant: resumeVariant } : {}),
tools: (() => {