From 010ce1cc06f02af2f9093d2044bc7de23c3ee528 Mon Sep 17 00:00:00 2001 From: ChiR24 Date: Sun, 4 Jan 2026 11:42:12 +0530 Subject: [PATCH 1/2] feat(todo-continuation-enforcer): include remaining todo list in system reminder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The system reminder now displays the actual remaining todos with their content, status indicators, and priority labels instead of just showing a count. This helps the agent know exactly what tasks need to be done. Format: 1. 🔄 [HIGH] Task in progress 2. ⏳ [MED] Pending task 3. ⏳ [LOW] Another pending task --- src/hooks/todo-continuation-enforcer.test.ts | 42 ++++++++++++++++++++ src/hooks/todo-continuation-enforcer.ts | 19 ++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/hooks/todo-continuation-enforcer.test.ts b/src/hooks/todo-continuation-enforcer.test.ts index 8f6c6f7e4f..2b8183b8e5 100644 --- a/src/hooks/todo-continuation-enforcer.test.ts +++ b/src/hooks/todo-continuation-enforcer.test.ts @@ -367,6 +367,48 @@ describe("todo-continuation-enforcer", () => { expect(toastCalls[0].message).toContain("2s") }) + test("should include remaining todo list in continuation prompt", async () => { + // #given - session with multiple incomplete todos + const sessionID = "main-todo-list" + setMainSession(sessionID) + + const mockInput = createMockPluginInput() + mockInput.client.session.todo = async () => ({ data: [ + { id: "1", content: "Fix authentication bug", status: "pending", priority: "high" }, + { id: "2", content: "Add unit tests", status: "in_progress", priority: "medium" }, + { id: "3", content: "Update documentation", status: "completed", priority: "low" }, + { id: "4", content: "Review code changes", status: "pending", priority: "high" }, + ]}) + + const hook = createTodoContinuationEnforcer(mockInput, { + backgroundManager: createMockBackgroundManager(false), + }) + + // #when - session goes idle and countdown completes + await hook.handler({ + event: { type: "session.idle", properties: { sessionID } }, + }) + await new Promise(r => setTimeout(r, 2500)) + + // #then - continuation prompt should include the remaining todo items + expect(promptCalls.length).toBe(1) + const promptText = promptCalls[0].text + + // Should contain status summary + expect(promptText).toContain("[Status: 1/4 completed, 3 remaining]") + + // Should contain remaining todos section + expect(promptText).toContain("Remaining Tasks:") + + // Should list pending tasks with content + expect(promptText).toContain("Fix authentication bug") + expect(promptText).toContain("Add unit tests") + expect(promptText).toContain("Review code changes") + + // Should NOT include completed task + expect(promptText).not.toContain("Update documentation") + }) + test("should not have 10s throttle between injections", async () => { // #given - new hook instance (no prior state) const sessionID = "main-no-throttle" diff --git a/src/hooks/todo-continuation-enforcer.ts b/src/hooks/todo-continuation-enforcer.ts index 5e16354d72..d902d1cd54 100644 --- a/src/hooks/todo-continuation-enforcer.ts +++ b/src/hooks/todo-continuation-enforcer.ts @@ -85,6 +85,23 @@ function isLastAssistantMessageAborted(messages: Array<{ info?: MessageInfo }>): return errorName === "MessageAbortedError" || errorName === "AbortError" } +function getIncompleteTodos(todos: Todo[]): Todo[] { + return todos.filter(t => t.status !== "completed" && t.status !== "cancelled") +} + +function formatRemainingTodos(todos: Todo[]): string { + const incompleteTodos = getIncompleteTodos(todos) + if (incompleteTodos.length === 0) return "" + + const lines = incompleteTodos.map((todo, idx) => { + const statusIndicator = todo.status === "in_progress" ? "🔄" : "⏳" + const priorityLabel = todo.priority === "high" ? "[HIGH]" : todo.priority === "medium" ? "[MED]" : "[LOW]" + return ` ${idx + 1}. ${statusIndicator} ${priorityLabel} ${todo.content}` + }) + + return `\n\nRemaining Tasks:\n${lines.join("\n")}` +} + export function createTodoContinuationEnforcer( ctx: PluginInput, options: TodoContinuationEnforcerOptions = {} @@ -198,7 +215,7 @@ export function createTodoContinuationEnforcer( return } - const prompt = `${CONTINUATION_PROMPT}\n\n[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]` + const prompt = `${CONTINUATION_PROMPT}\n\n[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]${formatRemainingTodos(todos)}` const modelField = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } From 8a56ecacc614a315594ab77e372ee57fc10e79b9 Mon Sep 17 00:00:00 2001 From: ChiR24 Date: Sun, 4 Jan 2026 12:04:05 +0530 Subject: [PATCH 2/2] fix: handle undefined priority and add format verification tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Handle undefined/null/empty priority with [NONE] label instead of incorrectly falling back to [LOW] - Add test assertions for status indicators (🔄/⏳) and priority labels - Add test case for undefined priority handling - Verify complete formatted entries like '🔄 [MED] Add unit tests' Addresses Copilot review comments on PR #473 --- src/hooks/todo-continuation-enforcer.test.ts | 53 ++++++++++++++++++++ src/hooks/todo-continuation-enforcer.ts | 9 +++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/hooks/todo-continuation-enforcer.test.ts b/src/hooks/todo-continuation-enforcer.test.ts index 2b8183b8e5..43a73062d5 100644 --- a/src/hooks/todo-continuation-enforcer.test.ts +++ b/src/hooks/todo-continuation-enforcer.test.ts @@ -407,6 +407,59 @@ describe("todo-continuation-enforcer", () => { // Should NOT include completed task expect(promptText).not.toContain("Update documentation") + + // Should include status indicators (🔄 for in_progress, ⏳ for pending) + expect(promptText).toContain("🔄") + expect(promptText).toContain("⏳") + + // Should include priority labels with correct format + expect(promptText).toContain("[HIGH]") + expect(promptText).toContain("[MED]") + + // Should have complete formatted entries + expect(promptText).toContain("🔄 [MED] Add unit tests") + expect(promptText).toContain("⏳ [HIGH] Fix authentication bug") + expect(promptText).toContain("⏳ [HIGH] Review code changes") + }) + + test("should handle undefined priority gracefully", async () => { + // #given - session with todos that have undefined/missing priority + const sessionID = "main-undefined-priority" + setMainSession(sessionID) + + const mockInput = createMockPluginInput() + mockInput.client.session.todo = async () => ({ data: [ + { id: "1", content: "Task with no priority", status: "pending", priority: undefined }, + { id: "2", content: "Task with null priority", status: "pending", priority: null }, + { id: "3", content: "Task with empty priority", status: "in_progress", priority: "" }, + { id: "4", content: "Normal high priority", status: "pending", priority: "high" }, + ]}) + + const hook = createTodoContinuationEnforcer(mockInput, { + backgroundManager: createMockBackgroundManager(false), + }) + + // #when - session goes idle and countdown completes + await hook.handler({ + event: { type: "session.idle", properties: { sessionID } }, + }) + await new Promise(r => setTimeout(r, 2500)) + + // #then - should handle undefined priority with [NONE] label + expect(promptCalls.length).toBe(1) + const promptText = promptCalls[0].text + + // Should contain all tasks + expect(promptText).toContain("Task with no priority") + expect(promptText).toContain("Task with null priority") + expect(promptText).toContain("Task with empty priority") + expect(promptText).toContain("Normal high priority") + + // Should have [NONE] for undefined/null/empty priorities + expect(promptText).toContain("[NONE]") + + // Should still have [HIGH] for the normal priority task + expect(promptText).toContain("[HIGH]") }) test("should not have 10s throttle between injections", async () => { diff --git a/src/hooks/todo-continuation-enforcer.ts b/src/hooks/todo-continuation-enforcer.ts index d902d1cd54..6253923d78 100644 --- a/src/hooks/todo-continuation-enforcer.ts +++ b/src/hooks/todo-continuation-enforcer.ts @@ -95,7 +95,14 @@ function formatRemainingTodos(todos: Todo[]): string { const lines = incompleteTodos.map((todo, idx) => { const statusIndicator = todo.status === "in_progress" ? "🔄" : "⏳" - const priorityLabel = todo.priority === "high" ? "[HIGH]" : todo.priority === "medium" ? "[MED]" : "[LOW]" + const priorityLabel = + todo.priority === "high" + ? "[HIGH]" + : todo.priority === "medium" + ? "[MED]" + : todo.priority === "low" + ? "[LOW]" + : "[NONE]" return ` ${idx + 1}. ${statusIndicator} ${priorityLabel} ${todo.content}` })