From 9e196a21d78e47f19d86bd268b2725b2b42da7bc Mon Sep 17 00:00:00 2001 From: ismeth Date: Mon, 2 Mar 2026 00:52:19 +0100 Subject: [PATCH] fix(council): fix nudge retry cap bypass and add memory cleanup --- .../council-continuation-enforcer.test.ts | 47 +++++++++++++++++++ .../council-continuation-enforcer.ts | 1 - 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/features/background-agent/council-continuation-enforcer.test.ts b/src/features/background-agent/council-continuation-enforcer.test.ts index 1a6c5697b..6d34e3420 100644 --- a/src/features/background-agent/council-continuation-enforcer.test.ts +++ b/src/features/background-agent/council-continuation-enforcer.test.ts @@ -358,6 +358,28 @@ describe("sendCouncilContinuationNudge", () => { resetCouncilNudgeCount(task.id) }) }) + + describe("#given promptAsync persistently fails", () => { + it("#when called MAX_NUDGE_ATTEMPTS times #then counter still reaches max and next call returns false", () => { + //#given + const client = { + session: { promptAsync: mock(() => Promise.reject(new Error("persistent network error"))) }, + } + const task = createRunningTask({ id: "task-nudge-persistent-fail" }) + resetCouncilNudgeCount(task.id) + + //#when - exhaust all 5 attempts despite every promptAsync failing + for (let i = 0; i < 5; i++) { + const result = sendCouncilContinuationNudge(client as never, task, task.sessionID!) + expect(result).toBe(true) + } + const resultAfterMax = sendCouncilContinuationNudge(client as never, task, task.sessionID!) + + //#then - counter was not rolled back, so max was reached + expect(resultAfterMax).toBe(false) + expect(client.session.promptAsync).toHaveBeenCalledTimes(5) + }) + }) }) describe("resetCouncilNudgeCount", () => { @@ -392,4 +414,29 @@ describe("resetCouncilNudgeCount", () => { resetCouncilNudgeCount(task.id) }) }) + + describe("#given nudge count exists for a task", () => { + it("#when resetCouncilNudgeCount is called #then memory is cleaned up and nudging restarts from zero", () => { + //#given + const client = createMockClient() + const task = createRunningTask({ id: "task-cleanup-test" }) + sendCouncilContinuationNudge(client as never, task, task.sessionID!) + sendCouncilContinuationNudge(client as never, task, task.sessionID!) + sendCouncilContinuationNudge(client as never, task, task.sessionID!) + + //#when + resetCouncilNudgeCount(task.id) + + //#then - counter is cleared, all 5 nudge attempts available again + const freshClient = createMockClient() + for (let i = 0; i < 5; i++) { + expect(sendCouncilContinuationNudge(freshClient as never, task, task.sessionID!)).toBe(true) + } + expect(sendCouncilContinuationNudge(freshClient as never, task, task.sessionID!)).toBe(false) + expect(freshClient.session.promptAsync).toHaveBeenCalledTimes(5) + + // cleanup + resetCouncilNudgeCount(task.id) + }) + }) }) diff --git a/src/features/background-agent/council-continuation-enforcer.ts b/src/features/background-agent/council-continuation-enforcer.ts index bf83521bc..d94124442 100644 --- a/src/features/background-agent/council-continuation-enforcer.ts +++ b/src/features/background-agent/council-continuation-enforcer.ts @@ -74,7 +74,6 @@ export function sendCouncilContinuationNudge( parts: [createInternalAgentTextPart(CONTINUATION_PROMPT)], }, }).catch((error) => { - nudgeCountByTask.set(task.id, count) log("[council-continuation] Nudge prompt error:", { taskId: task.id, error: String(error),