fix(agent): always keep agent when model is explicitly configured
Previously, when an explicit model was configured, the agent name was omitted to prevent opencode's built-in agent fallback chain from overriding the user-specified model. This removes that conditional logic and always passes the agent name alongside the model. Tests are updated to reflect this behavior change. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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("agent" in promptCalls[0].body).toBe(false)
|
||||
expect(promptCalls[0].body.agent).toBe("explore")
|
||||
})
|
||||
|
||||
test("should NOT pass model when task has no model (backward compatibility)", async () => {
|
||||
@@ -1832,7 +1832,7 @@ 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 () => {
|
||||
test("should keep agent when launch has model and keep agent without model", async () => {
|
||||
// given
|
||||
const promptBodies: Array<Record<string, unknown>> = []
|
||||
let resolveFirstPromptStarted: (() => void) | undefined
|
||||
@@ -1894,7 +1894,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
||||
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[0].agent).toBe("test-agent")
|
||||
expect(promptBodies[1].agent).toBe("test-agent")
|
||||
expect("model" in promptBodies[1]).toBe(false)
|
||||
})
|
||||
@@ -4752,6 +4752,53 @@ describe("BackgroundManager - tool permission spread order", () => {
|
||||
manager.shutdown()
|
||||
})
|
||||
|
||||
test("startTask keeps agent when explicit model is configured", async () => {
|
||||
//#given
|
||||
const promptCalls: Array<{ path: { id: string }; body: Record<string, unknown> }> = []
|
||||
const client = {
|
||||
session: {
|
||||
get: async () => ({ data: { directory: "/test/dir" } }),
|
||||
create: async () => ({ data: { id: "session-1" } }),
|
||||
promptAsync: async (args: { path: { id: string }; body: Record<string, unknown> }) => {
|
||||
promptCalls.push(args)
|
||||
return {}
|
||||
},
|
||||
},
|
||||
}
|
||||
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
|
||||
const task: BackgroundTask = {
|
||||
id: "task-explicit-model",
|
||||
status: "pending",
|
||||
queuedAt: new Date(),
|
||||
description: "test task",
|
||||
prompt: "test prompt",
|
||||
agent: "sisyphus-junior",
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4", variant: "medium" },
|
||||
}
|
||||
const input: import("./types").LaunchInput = {
|
||||
description: task.description,
|
||||
prompt: task.prompt,
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
model: task.model,
|
||||
}
|
||||
|
||||
//#when
|
||||
await (manager as unknown as { startTask: (item: { task: BackgroundTask; input: import("./types").LaunchInput }) => Promise<void> })
|
||||
.startTask({ task, input })
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(1)
|
||||
expect(promptCalls[0].body.agent).toBe("sisyphus-junior")
|
||||
expect(promptCalls[0].body.model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
|
||||
expect(promptCalls[0].body.variant).toBe("medium")
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
|
||||
test("resume respects explore agent restrictions", async () => {
|
||||
//#given
|
||||
let capturedTools: Record<string, unknown> | undefined
|
||||
@@ -4796,4 +4843,48 @@ describe("BackgroundManager - tool permission spread order", () => {
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
|
||||
test("resume keeps agent when explicit model is configured", async () => {
|
||||
//#given
|
||||
let promptCall: { path: { id: string }; body: Record<string, unknown> } | undefined
|
||||
const client = {
|
||||
session: {
|
||||
promptAsync: async (args: { path: { id: string }; body: Record<string, unknown> }) => {
|
||||
promptCall = args
|
||||
return {}
|
||||
},
|
||||
abort: async () => ({}),
|
||||
},
|
||||
}
|
||||
const manager = new BackgroundManager({ client, directory: tmpdir() } as unknown as PluginInput)
|
||||
const task: BackgroundTask = {
|
||||
id: "task-explicit-model-resume",
|
||||
sessionID: "session-3",
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
description: "resume task",
|
||||
prompt: "resume prompt",
|
||||
agent: "explore",
|
||||
status: "completed",
|
||||
startedAt: new Date(),
|
||||
completedAt: new Date(),
|
||||
model: { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" },
|
||||
}
|
||||
getTaskMap(manager).set(task.id, task)
|
||||
|
||||
//#when
|
||||
await manager.resume({
|
||||
sessionId: "session-3",
|
||||
prompt: "continue",
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
})
|
||||
|
||||
//#then
|
||||
expect(promptCall).toBeDefined()
|
||||
expect(promptCall?.body.agent).toBe("explore")
|
||||
expect(promptCall?.body.model).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" })
|
||||
|
||||
manager.shutdown()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -515,9 +515,7 @@ export class BackgroundManager {
|
||||
promptWithModelSuggestionRetry(this.client, {
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
// 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 }),
|
||||
agent: input.agent,
|
||||
...(launchModel ? { model: launchModel } : {}),
|
||||
...(launchVariant ? { variant: launchVariant } : {}),
|
||||
system: input.skillContent,
|
||||
@@ -794,9 +792,7 @@ export class BackgroundManager {
|
||||
this.client.session.promptAsync({
|
||||
path: { id: existingTask.sessionID },
|
||||
body: {
|
||||
// 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 }),
|
||||
agent: existingTask.agent,
|
||||
...(resumeModel ? { model: resumeModel } : {}),
|
||||
...(resumeVariant ? { variant: resumeVariant } : {}),
|
||||
tools: (() => {
|
||||
|
||||
@@ -64,4 +64,63 @@ describe("background-agent spawner.startTask", () => {
|
||||
{ permission: "question", action: "deny", pattern: "*" },
|
||||
])
|
||||
})
|
||||
|
||||
test("keeps agent when explicit model is configured", async () => {
|
||||
//#given
|
||||
const promptCalls: any[] = []
|
||||
|
||||
const client = {
|
||||
session: {
|
||||
get: async () => ({ data: { directory: "/parent/dir" } }),
|
||||
create: async () => ({ data: { id: "ses_child" } }),
|
||||
promptAsync: async (args?: any) => {
|
||||
promptCalls.push(args)
|
||||
return {}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const task = createTask({
|
||||
description: "Test task",
|
||||
prompt: "Do work",
|
||||
agent: "sisyphus-junior",
|
||||
parentSessionID: "ses_parent",
|
||||
parentMessageID: "msg_parent",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4", variant: "medium" },
|
||||
})
|
||||
|
||||
const item = {
|
||||
task,
|
||||
input: {
|
||||
description: task.description,
|
||||
prompt: task.prompt,
|
||||
agent: task.agent,
|
||||
parentSessionID: task.parentSessionID,
|
||||
parentMessageID: task.parentMessageID,
|
||||
parentModel: task.parentModel,
|
||||
parentAgent: task.parentAgent,
|
||||
model: task.model,
|
||||
},
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
client,
|
||||
directory: "/fallback",
|
||||
concurrencyManager: { release: () => {} },
|
||||
tmuxEnabled: false,
|
||||
onTaskError: () => {},
|
||||
}
|
||||
|
||||
//#when
|
||||
await startTask(item as any, ctx as any)
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(1)
|
||||
expect(promptCalls[0]?.body?.agent).toBe("sisyphus-junior")
|
||||
expect(promptCalls[0]?.body?.model).toEqual({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
})
|
||||
expect(promptCalls[0]?.body?.variant).toBe("medium")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -135,9 +135,7 @@ export async function startTask(
|
||||
promptWithModelSuggestionRetry(client, {
|
||||
path: { id: sessionID },
|
||||
body: {
|
||||
// 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 }),
|
||||
agent: input.agent,
|
||||
...(launchModel ? { model: launchModel } : {}),
|
||||
...(launchVariant ? { variant: launchVariant } : {}),
|
||||
system: input.skillContent,
|
||||
@@ -222,9 +220,7 @@ export async function resumeTask(
|
||||
client.session.promptAsync({
|
||||
path: { id: task.sessionID },
|
||||
body: {
|
||||
// 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: task.agent }),
|
||||
agent: task.agent,
|
||||
...(resumeModel ? { model: resumeModel } : {}),
|
||||
...(resumeVariant ? { variant: resumeVariant } : {}),
|
||||
tools: {
|
||||
|
||||
@@ -165,6 +165,55 @@ bunDescribe("sendSyncPrompt", () => {
|
||||
bunExpect(promptArgs.body.tools.call_omo_agent).toBe(true)
|
||||
})
|
||||
|
||||
bunTest("includes agent alongside explicit category model", async () => {
|
||||
//#given
|
||||
const { sendSyncPrompt } = require("./sync-prompt-sender")
|
||||
|
||||
let promptArgs: any
|
||||
const promptAsync = bunMock(async (input: any) => {
|
||||
promptArgs = input
|
||||
return { data: {} }
|
||||
})
|
||||
|
||||
const mockClient = {
|
||||
session: {
|
||||
promptAsync,
|
||||
},
|
||||
}
|
||||
|
||||
const input = {
|
||||
sessionID: "test-session",
|
||||
agentToUse: "sisyphus-junior",
|
||||
args: {
|
||||
description: "test task",
|
||||
prompt: "test prompt",
|
||||
category: "quick",
|
||||
run_in_background: false,
|
||||
load_skills: [],
|
||||
},
|
||||
systemContent: undefined,
|
||||
categoryModel: {
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
variant: "medium",
|
||||
},
|
||||
toastManager: null,
|
||||
taskId: undefined,
|
||||
}
|
||||
|
||||
//#when
|
||||
await sendSyncPrompt(mockClient, input)
|
||||
|
||||
//#then
|
||||
bunExpect(promptAsync).toHaveBeenCalled()
|
||||
bunExpect(promptArgs.body.agent).toBe("sisyphus-junior")
|
||||
bunExpect(promptArgs.body.model).toEqual({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
})
|
||||
bunExpect(promptArgs.body.variant).toBe("medium")
|
||||
})
|
||||
|
||||
bunTest("retries with promptSync for oracle when promptAsync fails with unexpected EOF", async () => {
|
||||
//#given
|
||||
const { sendSyncPrompt } = require("./sync-prompt-sender")
|
||||
|
||||
@@ -56,9 +56,7 @@ export async function sendSyncPrompt(
|
||||
const promptArgs = {
|
||||
path: { id: input.sessionID },
|
||||
body: {
|
||||
// When a custom model is configured, omit the agent name so opencode's
|
||||
// built-in agent fallback chain does not override the user-specified model.
|
||||
...(input.categoryModel ? {} : { agent: input.agentToUse }),
|
||||
agent: input.agentToUse,
|
||||
system: input.systemContent,
|
||||
tools,
|
||||
parts: [createInternalAgentTextPart(effectivePrompt)],
|
||||
|
||||
Reference in New Issue
Block a user