fix(session-recovery): recover interrupted idle tool turns

This commit is contained in:
YeonGyu-Kim
2026-05-17 15:08:39 +09:00
parent fbec112bc2
commit f43effb842
11 changed files with 531 additions and 14 deletions
+35
View File
@@ -373,6 +373,41 @@ describe("createEventHandler - idle deduplication", () => {
expect((dispatchCalls[1]?.event.properties as { sessionID?: string } | undefined)?.sessionID).toBe(sessionId)
})
it("#given idle recovery handles an interrupted tool turn #when session.idle arrives #then later idle hooks are skipped for that event", async () => {
const callOrder: string[] = []
const eventHandler = createEventHandler({
ctx: asEventHandlerContext({ directory: "/tmp" }),
pluginConfig: asPluginConfig({}),
firstMessageVariantGate: {
markSessionCreated: () => {},
clear: () => {},
},
managers: createEventHandlerManagers(),
hooks: createEventHandlerHooks({
sessionRecovery: {
handleInterruptedToolResultsOnIdle: async () => {
callOrder.push("sessionRecovery")
return true
},
},
todoContinuationEnforcer: {
handler: async () => {
callOrder.push("todoContinuationEnforcer")
},
},
}),
})
await eventHandler(asEventHandlerInput({
event: {
type: "session.idle",
properties: { sessionID: "ses_interrupted_idle" },
},
}))
expect(callOrder).toEqual(["sessionRecovery"])
})
it("keeps other session dedup state untouched when bypassing synthetic-idle for current session", async () => {
//#given
const originalDateNow = Date.now
+10
View File
@@ -571,6 +571,16 @@ export function createEventHandler(args: {
}
}
if (input.event.type === "session.idle") {
const sessionID = getEventSessionID(input);
if (sessionID && hooks.sessionRecovery?.handleInterruptedToolResultsOnIdle) {
const recovered = await hooks.sessionRecovery.handleInterruptedToolResultsOnIdle(sessionID);
if (recovered) {
return;
}
}
}
await dispatchToHooks(input);
const syntheticIdle = normalizeSessionStatusToIdle(input);