diff --git a/src/hooks/todo-continuation-enforcer/AGENTS.md b/src/hooks/todo-continuation-enforcer/AGENTS.md index 5df375c79..afcb5f3ac 100644 --- a/src/hooks/todo-continuation-enforcer/AGENTS.md +++ b/src/hooks/todo-continuation-enforcer/AGENTS.md @@ -38,7 +38,7 @@ session.idle ## CONSTANTS ```typescript -DEFAULT_SKIP_AGENTS = ["prometheus", "compaction"] +DEFAULT_SKIP_AGENTS = ["prometheus", "compaction", "plan"] CONTINUATION_COOLDOWN_MS = 30_000 // 30s between injections MAX_CONSECUTIVE_FAILURES = 5 // Then 5min pause (exponential backoff) FAILURE_RESET_WINDOW_MS = 5 * 60_000 // 5min window for failure reset diff --git a/src/hooks/todo-continuation-enforcer/constants.ts b/src/hooks/todo-continuation-enforcer/constants.ts index 30244ce9a..37f49e9b6 100644 --- a/src/hooks/todo-continuation-enforcer/constants.ts +++ b/src/hooks/todo-continuation-enforcer/constants.ts @@ -2,7 +2,7 @@ import { createSystemDirective, SystemDirectiveTypes } from "../../shared/system export const HOOK_NAME = "todo-continuation-enforcer" -export const DEFAULT_SKIP_AGENTS = ["prometheus", "compaction"] +export const DEFAULT_SKIP_AGENTS = ["prometheus", "compaction", "plan"] export const CONTINUATION_PROMPT = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)} diff --git a/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts b/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts index 0b628ca1e..f43a7c22d 100644 --- a/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts +++ b/src/hooks/todo-continuation-enforcer/continuation-injection.test.ts @@ -47,4 +47,38 @@ describe("injectContinuation", () => { expect(capturedTools).toEqual({ question: false, bash: true }) expect(capturedText).toContain(OMO_INTERNAL_INITIATOR_MARKER) }) + + test("skips injection when agent is plan (prevents Plan Mode infinite loop)", async () => { + // given + let injected = false + const ctx = { + directory: "/tmp/test", + client: { + session: { + todo: async () => ({ data: [{ id: "1", content: "todo", status: "pending", priority: "high" }] }), + promptAsync: async () => { + injected = true + return {} + }, + }, + }, + } + const sessionStateStore = { + getExistingState: () => ({ inFlight: false, lastInjectedAt: 0, consecutiveFailures: 0 }), + } + + // when + await injectContinuation({ + ctx: ctx as never, + sessionID: "ses_plan_skip", + resolvedInfo: { + agent: "plan", + model: { providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }, + }, + sessionStateStore: sessionStateStore as never, + }) + + // then + expect(injected).toBe(false) + }) })