From b0a484b40357ca05e00679477c712ab508a9058d Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 15 May 2026 11:30:07 +0900 Subject: [PATCH] fix(session-recovery): gate resume prompts --- .../recover-tool-result-missing.ts | 11 +++++++-- .../recover-unavailable-tool.ts | 18 ++++++++++++-- src/hooks/session-recovery/resume.ts | 24 ++++++++++++------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/hooks/session-recovery/recover-tool-result-missing.ts b/src/hooks/session-recovery/recover-tool-result-missing.ts index 6a1a8e6b9..60d5f7aec 100644 --- a/src/hooks/session-recovery/recover-tool-result-missing.ts +++ b/src/hooks/session-recovery/recover-tool-result-missing.ts @@ -3,11 +3,13 @@ import type { MessageData, ResumeConfig } from "./types" import { readParts } from "./storage" import { isSqliteBackend } from "../../shared/opencode-storage-detection" import { normalizeSDKResponse } from "../../shared" +import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate" type Client = ReturnType type ClientWithPromptAsync = { session: { promptAsync: (opts: { path: { id: string }; body: Record }) => Promise + status?: () => Promise } } @@ -119,9 +121,14 @@ export async function recoverToolResultMissing( return false } - await client.session.promptAsync(promptInput) + const promptResult = await promptAsyncAfterSessionIdle({ + client, + sessionID, + source: "session-recovery-tool-result-missing", + input: promptInput, + }) - return true + return promptResult.status === "dispatched" } catch { return false } diff --git a/src/hooks/session-recovery/recover-unavailable-tool.ts b/src/hooks/session-recovery/recover-unavailable-tool.ts index 3aa937e73..b45a4f7ff 100644 --- a/src/hooks/session-recovery/recover-unavailable-tool.ts +++ b/src/hooks/session-recovery/recover-unavailable-tool.ts @@ -4,6 +4,7 @@ import { readParts } from "./storage" import type { MessageData } from "./types" import { normalizeSDKResponse } from "../../shared" import { isSqliteBackend } from "../../shared/opencode-storage-detection" +import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate" type Client = ReturnType @@ -100,8 +101,21 @@ export async function recoverUnavailableTool( body: { parts: toolResultParts }, } const promptAsync = client.session.promptAsync as (...args: never[]) => unknown - await Reflect.apply(promptAsync, client.session, [promptInput]) - return true + const promptClient = { + session: { + status: client.session.status, + promptAsync: (input: PromptWithToolResultInput) => ( + Reflect.apply(promptAsync, client.session, [input]) as Promise + ), + }, + } + const promptResult = await promptAsyncAfterSessionIdle({ + client: promptClient, + sessionID, + source: "session-recovery-unavailable-tool", + input: promptInput, + }) + return promptResult.status === "dispatched" } catch { return false } diff --git a/src/hooks/session-recovery/resume.ts b/src/hooks/session-recovery/resume.ts index 136ea16b4..6049fdbcd 100644 --- a/src/hooks/session-recovery/resume.ts +++ b/src/hooks/session-recovery/resume.ts @@ -1,6 +1,7 @@ import type { createOpencodeClient } from "@opencode-ai/sdk" import type { MessageData, ResumeConfig } from "./types" import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared" +import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate" const RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]" @@ -32,17 +33,22 @@ export async function resumeSession(client: Client, config: ResumeConfig): Promi : undefined const launchVariant = config.model?.variant - await client.session.promptAsync({ - path: { id: config.sessionID }, - body: { - parts: [createInternalAgentContinuationTextPart(RECOVERY_RESUME_TEXT)], - agent: config.agent, - ...(launchModel ? { model: launchModel } : {}), - ...(launchVariant ? { variant: launchVariant } : {}), - ...(inheritedTools ? { tools: inheritedTools } : {}), + const promptResult = await promptAsyncAfterSessionIdle({ + client, + sessionID: config.sessionID, + source: "session-recovery", + input: { + path: { id: config.sessionID }, + body: { + parts: [createInternalAgentContinuationTextPart(RECOVERY_RESUME_TEXT)], + agent: config.agent, + ...(launchModel ? { model: launchModel } : {}), + ...(launchVariant ? { variant: launchVariant } : {}), + ...(inheritedTools ? { tools: inheritedTools } : {}), + }, }, }) - return true + return promptResult.status === "dispatched" } catch { return false }