fix(session-recovery): gate resume prompts

This commit is contained in:
YeonGyu-Kim
2026-05-15 11:30:07 +09:00
parent db28a32cff
commit b0a484b403
3 changed files with 40 additions and 13 deletions
@@ -3,11 +3,13 @@ import type { MessageData, ResumeConfig } from "./types"
import { readParts } from "./storage" import { readParts } from "./storage"
import { isSqliteBackend } from "../../shared/opencode-storage-detection" import { isSqliteBackend } from "../../shared/opencode-storage-detection"
import { normalizeSDKResponse } from "../../shared" import { normalizeSDKResponse } from "../../shared"
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
type Client = ReturnType<typeof createOpencodeClient> type Client = ReturnType<typeof createOpencodeClient>
type ClientWithPromptAsync = { type ClientWithPromptAsync = {
session: { session: {
promptAsync: (opts: { path: { id: string }; body: Record<string, unknown> }) => Promise<unknown> promptAsync: (opts: { path: { id: string }; body: Record<string, unknown> }) => Promise<unknown>
status?: () => Promise<unknown>
} }
} }
@@ -119,9 +121,14 @@ export async function recoverToolResultMissing(
return false 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 { } catch {
return false return false
} }
@@ -4,6 +4,7 @@ import { readParts } from "./storage"
import type { MessageData } from "./types" import type { MessageData } from "./types"
import { normalizeSDKResponse } from "../../shared" import { normalizeSDKResponse } from "../../shared"
import { isSqliteBackend } from "../../shared/opencode-storage-detection" import { isSqliteBackend } from "../../shared/opencode-storage-detection"
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
type Client = ReturnType<typeof createOpencodeClient> type Client = ReturnType<typeof createOpencodeClient>
@@ -100,8 +101,21 @@ export async function recoverUnavailableTool(
body: { parts: toolResultParts }, body: { parts: toolResultParts },
} }
const promptAsync = client.session.promptAsync as (...args: never[]) => unknown const promptAsync = client.session.promptAsync as (...args: never[]) => unknown
await Reflect.apply(promptAsync, client.session, [promptInput]) const promptClient = {
return true session: {
status: client.session.status,
promptAsync: (input: PromptWithToolResultInput) => (
Reflect.apply(promptAsync, client.session, [input]) as Promise<unknown>
),
},
}
const promptResult = await promptAsyncAfterSessionIdle<PromptWithToolResultInput>({
client: promptClient,
sessionID,
source: "session-recovery-unavailable-tool",
input: promptInput,
})
return promptResult.status === "dispatched"
} catch { } catch {
return false return false
} }
+15 -9
View File
@@ -1,6 +1,7 @@
import type { createOpencodeClient } from "@opencode-ai/sdk" import type { createOpencodeClient } from "@opencode-ai/sdk"
import type { MessageData, ResumeConfig } from "./types" import type { MessageData, ResumeConfig } from "./types"
import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared" import { createInternalAgentContinuationTextPart, resolveInheritedPromptTools } from "../../shared"
import { promptAsyncAfterSessionIdle } from "../shared/prompt-async-gate"
const RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]" const RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]"
@@ -32,17 +33,22 @@ export async function resumeSession(client: Client, config: ResumeConfig): Promi
: undefined : undefined
const launchVariant = config.model?.variant const launchVariant = config.model?.variant
await client.session.promptAsync({ const promptResult = await promptAsyncAfterSessionIdle({
path: { id: config.sessionID }, client,
body: { sessionID: config.sessionID,
parts: [createInternalAgentContinuationTextPart(RECOVERY_RESUME_TEXT)], source: "session-recovery",
agent: config.agent, input: {
...(launchModel ? { model: launchModel } : {}), path: { id: config.sessionID },
...(launchVariant ? { variant: launchVariant } : {}), body: {
...(inheritedTools ? { tools: inheritedTools } : {}), parts: [createInternalAgentContinuationTextPart(RECOVERY_RESUME_TEXT)],
agent: config.agent,
...(launchModel ? { model: launchModel } : {}),
...(launchVariant ? { variant: launchVariant } : {}),
...(inheritedTools ? { tools: inheritedTools } : {}),
},
}, },
}) })
return true return promptResult.status === "dispatched"
} catch { } catch {
return false return false
} }