diff --git a/src/features/background-agent/manager.ts b/src/features/background-agent/manager.ts index 16c88d75..93d39804 100644 --- a/src/features/background-agent/manager.ts +++ b/src/features/background-agent/manager.ts @@ -826,13 +826,13 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea try { const messagesResp = await this.client.session.messages({ path: { id: task.parentSessionID } }) const messages = (messagesResp.data ?? []) as Array<{ - info?: { agent?: string; model?: { providerID: string; modelID: string } } + info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string } }> for (let i = messages.length - 1; i >= 0; i--) { const info = messages[i].info - if (info?.agent || info?.model) { + if (info?.agent || info?.model || (info?.modelID && info?.providerID)) { agent = info.agent ?? task.parentAgent - model = info.model + model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined) break } } diff --git a/src/hooks/ralph-loop/index.ts b/src/hooks/ralph-loop/index.ts index c2b4de32..17783dee 100644 --- a/src/hooks/ralph-loop/index.ts +++ b/src/hooks/ralph-loop/index.ts @@ -322,13 +322,13 @@ export function createRalphLoopHook( try { const messagesResp = await ctx.client.session.messages({ path: { id: sessionID } }) const messages = (messagesResp.data ?? []) as Array<{ - info?: { agent?: string; model?: { providerID: string; modelID: string } } + info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string } }> for (let i = messages.length - 1; i >= 0; i--) { const info = messages[i].info - if (info?.agent || info?.model) { + if (info?.agent || info?.model || (info?.modelID && info?.providerID)) { agent = info.agent - model = info.model + model = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined) break } } diff --git a/src/hooks/todo-continuation-enforcer.test.ts b/src/hooks/todo-continuation-enforcer.test.ts index 2f693b8c..bf3343be 100644 --- a/src/hooks/todo-continuation-enforcer.test.ts +++ b/src/hooks/todo-continuation-enforcer.test.ts @@ -829,4 +829,50 @@ describe("todo-continuation-enforcer", () => { expect(promptCalls[0].text).toContain("TODO CONTINUATION") expect("model" in promptCalls[0]).toBe(true) }) + + test("should extract model from assistant message with flat modelID/providerID", async () => { + // #given - session with assistant message that has flat modelID/providerID (OpenCode API format) + const sessionID = "main-assistant-model" + setMainSession(sessionID) + + // OpenCode returns assistant messages with flat modelID/providerID, not nested model object + const mockMessagesWithAssistant = [ + { info: { id: "msg-1", role: "user", agent: "Sisyphus", model: { providerID: "openai", modelID: "gpt-5.2" } } }, + { info: { id: "msg-2", role: "assistant", agent: "Sisyphus", modelID: "gpt-5.2", providerID: "openai" } }, + ] + + const mockInput = { + client: { + session: { + todo: async () => ({ + data: [{ id: "1", content: "Task 1", status: "pending", priority: "high" }], + }), + messages: async () => ({ data: mockMessagesWithAssistant }), + prompt: async (opts: any) => { + promptCalls.push({ + sessionID: opts.path.id, + agent: opts.body.agent, + model: opts.body.model, + text: opts.body.parts[0].text, + }) + return {} + }, + }, + tui: { showToast: async () => ({}) }, + }, + directory: "/tmp/test", + } as any + + const hook = createTodoContinuationEnforcer(mockInput, { + backgroundManager: createMockBackgroundManager(false), + }) + + // #when - session goes idle + await hook.handler({ event: { type: "session.idle", properties: { sessionID } } }) + await new Promise(r => setTimeout(r, 2500)) + + // #then - model should be extracted from assistant message's flat modelID/providerID + expect(promptCalls.length).toBe(1) + expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.2" }) + }) }) diff --git a/src/hooks/todo-continuation-enforcer.ts b/src/hooks/todo-continuation-enforcer.ts index 161b88ff..1841b56d 100644 --- a/src/hooks/todo-continuation-enforcer.ts +++ b/src/hooks/todo-continuation-enforcer.ts @@ -381,15 +381,17 @@ export function createTodoContinuationEnforcer( info?: { agent?: string model?: { providerID: string; modelID: string } + modelID?: string + providerID?: string tools?: Record } }> for (let i = messages.length - 1; i >= 0; i--) { const info = messages[i].info - if (info?.agent || info?.model) { + if (info?.agent || info?.model || (info?.modelID && info?.providerID)) { resolvedInfo = { agent: info.agent, - model: info.model, + model: info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined), tools: info.tools, } break diff --git a/src/tools/sisyphus-task/tools.ts b/src/tools/sisyphus-task/tools.ts index a1e1730f..612f4394 100644 --- a/src/tools/sisyphus-task/tools.ts +++ b/src/tools/sisyphus-task/tools.ts @@ -297,13 +297,13 @@ Use \`background_output\` with task_id="${task.id}" to check progress.` try { const messagesResp = await client.session.messages({ path: { id: args.resume } }) const messages = (messagesResp.data ?? []) as Array<{ - info?: { agent?: string; model?: { providerID: string; modelID: string } } + info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string } }> for (let i = messages.length - 1; i >= 0; i--) { const info = messages[i].info - if (info?.agent || info?.model) { + if (info?.agent || info?.model || (info?.modelID && info?.providerID)) { resumeAgent = info.agent - resumeModel = info.model + resumeModel = info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined) break } }