fix: trigger compaction before continue after session error recovery (fixes #390)
This commit is contained in:
@@ -585,3 +585,123 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
|
|||||||
expect(promptCalls).toEqual([sessionID, sessionID])
|
expect(promptCalls).toEqual([sessionID, sessionID])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("createEventHandler - session recovery compaction", () => {
|
||||||
|
it("triggers compaction before sending continue after session error recovery", async () => {
|
||||||
|
//#given
|
||||||
|
const sessionID = "ses_recovery_compaction"
|
||||||
|
setMainSession(sessionID)
|
||||||
|
const callOrder: string[] = []
|
||||||
|
|
||||||
|
const eventHandler = createEventHandler({
|
||||||
|
ctx: {
|
||||||
|
directory: "/tmp",
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
abort: async () => ({}),
|
||||||
|
summarize: async () => {
|
||||||
|
callOrder.push("summarize")
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
prompt: async () => {
|
||||||
|
callOrder.push("prompt")
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
pluginConfig: {} as any,
|
||||||
|
firstMessageVariantGate: {
|
||||||
|
markSessionCreated: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
},
|
||||||
|
managers: {
|
||||||
|
tmuxSessionManager: {
|
||||||
|
onSessionCreated: async () => {},
|
||||||
|
onSessionDeleted: async () => {},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
hooks: {
|
||||||
|
sessionRecovery: {
|
||||||
|
isRecoverableError: () => true,
|
||||||
|
handleSessionRecovery: async () => true,
|
||||||
|
},
|
||||||
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
|
} as any,
|
||||||
|
})
|
||||||
|
|
||||||
|
//#when
|
||||||
|
await eventHandler({
|
||||||
|
event: {
|
||||||
|
type: "session.error",
|
||||||
|
properties: {
|
||||||
|
sessionID,
|
||||||
|
messageID: "msg_123",
|
||||||
|
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
//#then - summarize (compaction) must be called before prompt (continue)
|
||||||
|
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("sends continue even if compaction fails", async () => {
|
||||||
|
//#given
|
||||||
|
const sessionID = "ses_recovery_compaction_fail"
|
||||||
|
setMainSession(sessionID)
|
||||||
|
const callOrder: string[] = []
|
||||||
|
|
||||||
|
const eventHandler = createEventHandler({
|
||||||
|
ctx: {
|
||||||
|
directory: "/tmp",
|
||||||
|
client: {
|
||||||
|
session: {
|
||||||
|
abort: async () => ({}),
|
||||||
|
summarize: async () => {
|
||||||
|
callOrder.push("summarize")
|
||||||
|
throw new Error("compaction failed")
|
||||||
|
},
|
||||||
|
prompt: async () => {
|
||||||
|
callOrder.push("prompt")
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
pluginConfig: {} as any,
|
||||||
|
firstMessageVariantGate: {
|
||||||
|
markSessionCreated: () => {},
|
||||||
|
clear: () => {},
|
||||||
|
},
|
||||||
|
managers: {
|
||||||
|
tmuxSessionManager: {
|
||||||
|
onSessionCreated: async () => {},
|
||||||
|
onSessionDeleted: async () => {},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
hooks: {
|
||||||
|
sessionRecovery: {
|
||||||
|
isRecoverableError: () => true,
|
||||||
|
handleSessionRecovery: async () => true,
|
||||||
|
},
|
||||||
|
stopContinuationGuard: { isStopped: () => false },
|
||||||
|
} as any,
|
||||||
|
})
|
||||||
|
|
||||||
|
//#when
|
||||||
|
await eventHandler({
|
||||||
|
event: {
|
||||||
|
type: "session.error",
|
||||||
|
properties: {
|
||||||
|
sessionID,
|
||||||
|
messageID: "msg_456",
|
||||||
|
error: { name: "Error", message: "tool_result block(s) that are not immediately" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any)
|
||||||
|
|
||||||
|
//#then - continue is still sent even when compaction fails
|
||||||
|
expect(callOrder).toEqual(["summarize", "prompt"])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -148,6 +148,8 @@ export function createEventHandler(args: {
|
|||||||
body: { parts: Array<{ type: "text"; text: string }> };
|
body: { parts: Array<{ type: "text"; text: string }> };
|
||||||
query: { directory: string };
|
query: { directory: string };
|
||||||
}) => Promise<unknown>;
|
}) => Promise<unknown>;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
summarize: (...args: any[]) => Promise<unknown>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -502,6 +504,17 @@ export function createEventHandler(args: {
|
|||||||
sessionID === getMainSessionID() &&
|
sessionID === getMainSessionID() &&
|
||||||
!hooks.stopContinuationGuard?.isStopped(sessionID)
|
!hooks.stopContinuationGuard?.isStopped(sessionID)
|
||||||
) {
|
) {
|
||||||
|
// Trigger compaction before sending "continue" to avoid double-sending continuation
|
||||||
|
await pluginContext.client.session
|
||||||
|
.summarize({
|
||||||
|
path: { id: sessionID },
|
||||||
|
body: { auto: true },
|
||||||
|
query: { directory: pluginContext.directory },
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
log("[event] compaction before recovery continue failed:", { sessionID, error: err });
|
||||||
|
});
|
||||||
|
|
||||||
await pluginContext.client.session
|
await pluginContext.client.session
|
||||||
.prompt({
|
.prompt({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
|
|||||||
Reference in New Issue
Block a user