fix(recovery): avoid duplicate continuation prompts
This commit is contained in:
@@ -196,7 +196,7 @@ describe("createCompactionContextInjector recovery", () => {
|
||||
expect(recoveryPart?.metadata).toEqual({ compaction_continue: true })
|
||||
})
|
||||
|
||||
it("retries recovery when the recovered prompt config still mismatches expected model or tools", async () => {
|
||||
it("does not immediately retry recovery when the recovered prompt config still mismatches expected model or tools", async () => {
|
||||
//#given
|
||||
const promptAsyncRecorder = createPromptAsyncRecorder()
|
||||
const mismatchResponse = [
|
||||
@@ -247,7 +247,61 @@ describe("createCompactionContextInjector recovery", () => {
|
||||
})
|
||||
|
||||
//#then
|
||||
expect(promptAsyncRecorder.calls.length).toBe(2)
|
||||
expect(promptAsyncRecorder.calls.length).toBe(1)
|
||||
})
|
||||
|
||||
it("#given post-dispatch config read is stale #when a second compaction event arrives immediately #then recovery prompt is not duplicated", async () => {
|
||||
//#given
|
||||
const promptAsyncRecorder = createPromptAsyncRecorder()
|
||||
const checkpointedPromptConfig = [
|
||||
{
|
||||
info: {
|
||||
role: "user",
|
||||
agent: "atlas",
|
||||
model: { providerID: "openai", modelID: "gpt-5" },
|
||||
tools: { bash: true },
|
||||
},
|
||||
},
|
||||
]
|
||||
const incompletePromptConfig = [
|
||||
{
|
||||
info: {
|
||||
role: "user",
|
||||
agent: "atlas",
|
||||
model: { providerID: "openai", modelID: "gpt-5" },
|
||||
},
|
||||
},
|
||||
]
|
||||
const ctx = createMockContext(
|
||||
[
|
||||
checkpointedPromptConfig,
|
||||
incompletePromptConfig,
|
||||
incompletePromptConfig,
|
||||
incompletePromptConfig,
|
||||
incompletePromptConfig,
|
||||
incompletePromptConfig,
|
||||
],
|
||||
promptAsyncRecorder.promptAsync,
|
||||
)
|
||||
const injector = createCompactionContextInjector({ ctx })
|
||||
|
||||
//#when
|
||||
await injector.capture("ses_stale_recovery_read")
|
||||
await injector.event({
|
||||
event: {
|
||||
type: "session.compacted",
|
||||
properties: { sessionID: "ses_stale_recovery_read" },
|
||||
},
|
||||
})
|
||||
await injector.event({
|
||||
event: {
|
||||
type: "session.compacted",
|
||||
properties: { sessionID: "ses_stale_recovery_read" },
|
||||
},
|
||||
})
|
||||
|
||||
//#then
|
||||
expect(promptAsyncRecorder.calls.length).toBe(1)
|
||||
})
|
||||
|
||||
it("does not treat reasoning-only assistant messages as a no-text tail", async () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
import { AGENT_RECOVERY_PROMPT, NO_TEXT_TAIL_THRESHOLD, RECOVERY_COOLDOWN_MS, RECENT_COMPACTION_WINDOW_MS } from "./constants"
|
||||
import type { CompactionContextClient } from "./types"
|
||||
import type { TailMonitorState } from "./tail-monitor"
|
||||
import { dispatchInternalPrompt, releasePromptAsyncReservation } from "../shared/prompt-async-gate"
|
||||
import { dispatchInternalPrompt } from "../shared/prompt-async-gate"
|
||||
|
||||
export function createRecoveryLogic(
|
||||
ctx: CompactionContextClient | undefined,
|
||||
@@ -107,6 +107,7 @@ export function createRecoveryLogic(
|
||||
})
|
||||
return false
|
||||
}
|
||||
tailState.lastRecoveryAt = now
|
||||
|
||||
const recoveredPromptConfig = await resolveLatestSessionPromptConfig(ctx, sessionID)
|
||||
if (!isPromptConfigRecovered(recoveredPromptConfig, expectedPromptConfig)) {
|
||||
@@ -118,9 +119,6 @@ export function createRecoveryLogic(
|
||||
hasTools: !!tools,
|
||||
recoveredPromptConfig,
|
||||
})
|
||||
releasePromptAsyncReservation(sessionID, "compaction-context-injector:incomplete-recovery", {
|
||||
reservedBy: "compaction-context-injector",
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -132,7 +130,6 @@ export function createRecoveryLogic(
|
||||
setSessionTools(sessionID, tools)
|
||||
}
|
||||
|
||||
tailState.lastRecoveryAt = now
|
||||
tailState.consecutiveNoTextMessages = 0
|
||||
|
||||
log(`[compaction-context-injector] Re-injected checkpointed agent config`, {
|
||||
|
||||
Reference in New Issue
Block a user