diff --git a/src/hooks/todo-continuation-enforcer/continuation-injection.ts b/src/hooks/todo-continuation-enforcer/continuation-injection.ts index b48770ab..ba60d23b 100644 --- a/src/hooks/todo-continuation-enforcer/continuation-injection.ts +++ b/src/hooks/todo-continuation-enforcer/continuation-injection.ts @@ -37,6 +37,7 @@ export async function injectContinuation(args: { skipAgents?: string[] resolvedInfo?: ResolvedMessageInfo sessionStateStore: SessionStateStore + isContinuationStopped?: (sessionID: string) => boolean }): Promise { const { ctx, @@ -45,6 +46,7 @@ export async function injectContinuation(args: { skipAgents = DEFAULT_SKIP_AGENTS, resolvedInfo, sessionStateStore, + isContinuationStopped, } = args const state = sessionStateStore.getExistingState(sessionID) @@ -53,6 +55,11 @@ export async function injectContinuation(args: { return } + if (isContinuationStopped?.(sessionID)) { + log(`[${HOOK_NAME}] Skipped injection: continuation stopped for session`, { sessionID }) + return + } + const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task: { status: string }) => task.status === "running") : false diff --git a/src/hooks/todo-continuation-enforcer/countdown.ts b/src/hooks/todo-continuation-enforcer/countdown.ts index 7404d32d..007e7dc3 100644 --- a/src/hooks/todo-continuation-enforcer/countdown.ts +++ b/src/hooks/todo-continuation-enforcer/countdown.ts @@ -38,6 +38,7 @@ export function startCountdown(args: { backgroundManager?: BackgroundManager skipAgents: string[] sessionStateStore: SessionStateStore + isContinuationStopped?: (sessionID: string) => boolean }): void { const { ctx, @@ -47,6 +48,7 @@ export function startCountdown(args: { backgroundManager, skipAgents, sessionStateStore, + isContinuationStopped, } = args const state = sessionStateStore.getState(sessionID) @@ -72,6 +74,7 @@ export function startCountdown(args: { skipAgents, resolvedInfo, sessionStateStore, + isContinuationStopped, }) }, COUNTDOWN_SECONDS * 1000) diff --git a/src/hooks/todo-continuation-enforcer/idle-event.ts b/src/hooks/todo-continuation-enforcer/idle-event.ts index 689672c0..10708d1a 100644 --- a/src/hooks/todo-continuation-enforcer/idle-event.ts +++ b/src/hooks/todo-continuation-enforcer/idle-event.ts @@ -187,5 +187,6 @@ export async function handleSessionIdle(args: { backgroundManager, skipAgents, sessionStateStore, + isContinuationStopped, }) } diff --git a/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts b/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts index 10f23ff3..defe8c8b 100644 --- a/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts +++ b/src/hooks/todo-continuation-enforcer/todo-continuation-enforcer.test.ts @@ -1608,6 +1608,31 @@ describe("todo-continuation-enforcer", () => { expect(promptCalls).toHaveLength(0) }) + test("should not inject when isContinuationStopped becomes true during countdown", async () => { + // given - session where continuation is not stopped at idle time but stops during countdown + const sessionID = "main-race-condition" + setMainSession(sessionID) + let stopped = false + + const hook = createTodoContinuationEnforcer(createMockPluginInput(), { + isContinuationStopped: () => stopped, + }) + + // when - session goes idle with continuation not yet stopped + await hook.handler({ + event: { type: "session.idle", properties: { sessionID } }, + }) + + // when - stop-continuation fires during the 2s countdown window + stopped = true + + // when - countdown elapses and injectContinuation fires + await fakeTimers.advanceBy(3000) + + // then - no injection because isContinuationStopped became true before injectContinuation ran + expect(promptCalls).toHaveLength(0) + }) + test("should inject when isContinuationStopped returns false", async () => { fakeTimers.restore() // given - session with continuation not stopped