feat(hooks): adapt session-recovery for team-mode session semantics
This commit is contained in:
@@ -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") {
|
||||
|
||||
@@ -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<string, unknown> }
|
||||
expect(call.body).not.toHaveProperty("agent")
|
||||
expect(call.body).not.toHaveProperty("model")
|
||||
expect(call.body).not.toHaveProperty("variant")
|
||||
})
|
||||
})
|
||||
|
||||
export {}
|
||||
|
||||
@@ -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<boolean> {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user