diff --git a/src/hooks/session-recovery/hook.ts b/src/hooks/session-recovery/hook.ts index 833dcf5dd..aac3abc8e 100644 --- a/src/hooks/session-recovery/hook.ts +++ b/src/hooks/session-recovery/hook.ts @@ -123,7 +123,9 @@ export function createSessionRecoveryHook(ctx: PluginInput, options?: SessionRec let success = false if (errorType === "tool_result_missing") { - success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg) + const lastUser = findLastUserMessage(msgs ?? []) + const resumeConfig = extractResumeConfig(lastUser, sessionID) + success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg, resumeConfig) } else if (errorType === "unavailable_tool") { success = await recoverUnavailableTool(ctx.client, sessionID, failedMsg) } else if (errorType === "thinking_block_order") { diff --git a/src/hooks/session-recovery/recover-tool-result-missing.test.ts b/src/hooks/session-recovery/recover-tool-result-missing.test.ts index a720ef079..430f4e719 100644 --- a/src/hooks/session-recovery/recover-tool-result-missing.test.ts +++ b/src/hooks/session-recovery/recover-tool-result-missing.test.ts @@ -129,6 +129,63 @@ describe("recoverToolResultMissing", () => { }, }) }) + + it("pins agent, model, and variant on promptAsync body when resumeConfig provides them", async () => { + // given + storedParts = [{ + type: "tool", + id: "prt_stored_pin_call", + callID: "toolu_pin", + tool: "bash", + state: { input: {} }, + }] + const { client, promptAsync } = createMockClient() + const resumeConfig = { + sessionID: "ses_pin", + agent: "Hephaestus", + model: { providerID: "openai", modelID: "gpt-5.3-codex", variant: "max" }, + } + + // when + const result = await recoverToolResultMissing(client, "ses_pin", failedAssistantMsg, resumeConfig) + + // then + expect(result).toBe(true) + expect(promptAsync).toHaveBeenCalledTimes(1) + const call = promptAsync.mock.calls[0]?.[0] as { + body: { + agent?: string + model?: { providerID: string; modelID: string } + variant?: string + parts: unknown[] + } + } + expect(call.body.agent).toBe("Hephaestus") + expect(call.body.model).toEqual({ providerID: "openai", modelID: "gpt-5.3-codex" }) + expect(call.body.variant).toBe("max") + }) + + it("leaves body unchanged when no resumeConfig is provided", async () => { + // given + storedParts = [{ + type: "tool", + id: "prt_stored_nopin_call", + callID: "toolu_nopin", + tool: "bash", + state: { input: {} }, + }] + const { client, promptAsync } = createMockClient() + + // when + const result = await recoverToolResultMissing(client, "ses_nopin", failedAssistantMsg) + + // then + expect(result).toBe(true) + const call = promptAsync.mock.calls[0]?.[0] as { body: Record } + expect(call.body).not.toHaveProperty("agent") + expect(call.body).not.toHaveProperty("model") + expect(call.body).not.toHaveProperty("variant") + }) }) export {} diff --git a/src/hooks/session-recovery/recover-tool-result-missing.ts b/src/hooks/session-recovery/recover-tool-result-missing.ts index c3d12da53..0e7912571 100644 --- a/src/hooks/session-recovery/recover-tool-result-missing.ts +++ b/src/hooks/session-recovery/recover-tool-result-missing.ts @@ -1,5 +1,5 @@ import type { createOpencodeClient } from "@opencode-ai/sdk" -import type { MessageData } from "./types" +import type { MessageData, ResumeConfig } from "./types" import { readParts } from "./storage" import { isSqliteBackend } from "../../shared/opencode-storage-detection" import { normalizeSDKResponse } from "../../shared" @@ -70,7 +70,8 @@ async function readPartsFromSDKFallback( export async function recoverToolResultMissing( client: Client, sessionID: string, - failedAssistantMsg: MessageData + failedAssistantMsg: MessageData, + resumeConfig?: ResumeConfig ): Promise { let parts = failedAssistantMsg.parts || [] if (parts.length === 0 && failedAssistantMsg.info?.id) { @@ -93,9 +94,20 @@ export async function recoverToolResultMissing( content: "Operation cancelled by user (ESC pressed)", })) + const launchAgent = resumeConfig?.agent + const launchModel = resumeConfig?.model + ? { providerID: resumeConfig.model.providerID, modelID: resumeConfig.model.modelID } + : undefined + const launchVariant = resumeConfig?.model?.variant + const promptInput = { path: { id: sessionID }, - body: { parts: toolResultParts }, + body: { + parts: toolResultParts, + ...(launchAgent ? { agent: launchAgent } : {}), + ...(launchModel ? { model: launchModel } : {}), + ...(launchVariant ? { variant: launchVariant } : {}), + }, } try {