From dc4aeb7b88fd2ffb16285a1e9cd1890c6ef59a68 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 4 Jun 2026 02:44:35 +0200 Subject: [PATCH] refactor(coding-agent): renamed todo_write tool to todo - Renamed `TodoWriteTool` to `TodoTool` and its source/prompt files. - Updated tool registration, schema, renderers, and gating to `todo`. - Adjusted cursor provider native tool names and tests to match. - Renamed strike-animation constants and `todo-error-reminder` type. --- README.md | 2 +- docs/tools/task.md | 2 +- docs/tools/{todo_write.md => todo.md} | 24 +++---- packages/agent/src/types.ts | 2 +- packages/ai/src/providers/cursor.ts | 16 ++--- packages/ai/test/anthropic-alignment.test.ts | 2 +- .../ai/test/anthropic-stream-timeout.test.ts | 4 +- .../ai/test/duplicate-tool-results.test.ts | 4 +- packages/ai/test/google-tool-schema.test.ts | 4 +- packages/ai/test/openai-codex-stream.test.ts | 10 +-- .../test/openai-first-event-timeout.test.ts | 4 +- .../ai/test/tool-argument-coercion.test.ts | 4 +- packages/coding-agent/CHANGELOG.md | 60 ++++++++--------- packages/coding-agent/DEVELOPMENT.md | 4 +- packages/coding-agent/src/cli/args.ts | 2 +- .../src/config/settings-schema.ts | 2 +- .../src/export/html/template.generated.ts | 2 +- .../coding-agent/src/export/html/template.js | 6 +- .../src/extensibility/custom-tools/types.ts | 2 +- .../src/extensibility/shared-events.ts | 2 +- .../src/modes/acp/acp-event-mapper.ts | 14 ++-- .../src/modes/components/todo-reminder.ts | 2 +- .../src/modes/components/tool-execution.ts | 6 +- .../src/modes/controllers/event-controller.ts | 8 +-- .../controllers/todo-command-controller.ts | 2 +- .../src/modes/interactive-mode.ts | 4 +- .../coding-agent/src/modes/rpc/rpc-types.ts | 2 +- .../src/prompts/goals/goal-continuation.md | 2 +- .../src/prompts/system/eager-todo.md | 6 +- .../src/prompts/system/orchestrate-notice.md | 10 +-- .../src/prompts/system/plan-mode-approved.md | 8 +-- .../src/prompts/system/workflow-notice.md | 2 +- .../prompts/tools/{todo-write.md => todo.md} | 0 .../coding-agent/src/session/agent-session.ts | 28 ++++---- .../src/session/redis-session-storage.ts | 6 +- .../src/session/session-storage.ts | 2 - .../src/session/sql-session-storage.ts | 10 ++- .../src/slash-commands/helpers/todo.ts | 4 +- packages/coding-agent/src/task/executor.ts | 2 +- packages/coding-agent/src/tools/index.ts | 8 +-- packages/coding-agent/src/tools/renderers.ts | 4 +- .../src/tools/{todo-write.ts => todo.ts} | 67 +++++++++---------- packages/coding-agent/test/acp-agent.test.ts | 10 +-- .../test/agent-session-eager-todo.test.ts | 26 +++---- .../agent-session-new-session-todos.test.ts | 4 +- .../test/interactive-mode-todo-clear.test.ts | 2 +- .../test/system-prompt-templates.test.ts | 4 +- .../coding-agent/test/tools/index.test.ts | 2 +- .../provider-schema-compatibility.test.ts | 4 +- .../{todo-write.test.ts => todo.test.ts} | 40 +++++------ packages/tui/src/terminal.ts | 5 +- 51 files changed, 227 insertions(+), 225 deletions(-) rename docs/tools/{todo_write.md => todo.md} (89%) rename packages/coding-agent/src/prompts/tools/{todo-write.md => todo.md} (100%) rename packages/coding-agent/src/tools/{todo-write.ts => todo.ts} (93%) rename packages/coding-agent/test/tools/{todo-write.test.ts => todo.test.ts} (92%) diff --git a/README.md b/README.md index cf28855c2..f8c32929a 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ Stealth's on by default, so pages see a normal user instead of a headless bot. T - `task` — fan out subagents in parallel, optionally workspace-isolated. - `irc` — short prose between live agents in this process. -- `todo_write` — ordered mutations over the session todo list with phase tracking. +- `todo` — ordered mutations over the session todo list with phase tracking. - `job` — wait on or cancel background jobs. - `ask` — structured follow-up questions for interactive runs. diff --git a/docs/tools/task.md b/docs/tools/task.md index 98026680c..a03e492b1 100644 --- a/docs/tools/task.md +++ b/docs/tools/task.md @@ -114,7 +114,7 @@ Artifacts and side channels: - auto-add `task` when the agent has `spawns` and recursion depth allows it - remove `task` at or past `task.maxRecursionDepth` - expand `exec` to `eval` and `bash` - - strip parent-owned `todo_write` after session creation + - strip parent-owned `todo` after session creation 17. `runSubprocess(...)` subscribes to child agent events, coalesces progress updates every 150 ms, forwards lifecycle/progress events on the parent event bus, and extracts tool data through `subprocessToolRegistry`. 18. The child must finish through the hidden `yield` tool. If it does not, `runSubprocess(...)` sends up to 3 reminder prompts; the last reminder forces `toolChoice = yield` when supported. 19. Finalization uses `finalizeSubprocessOutput(...)` to reconcile raw assistant text, `yield` payloads, structured schemas, `report_finding` data, and abort states. Output is truncated with `MAX_OUTPUT_BYTES` / `MAX_OUTPUT_LINES` before returning to the parent, but the full raw output is still written to `.md`. diff --git a/docs/tools/todo_write.md b/docs/tools/todo.md similarity index 89% rename from docs/tools/todo_write.md rename to docs/tools/todo.md index 4a96fe100..7c2635f09 100644 --- a/docs/tools/todo_write.md +++ b/docs/tools/todo.md @@ -1,10 +1,10 @@ -# todo_write +# todo > Applies ordered mutations to the session todo list and returns a text summary plus the full phase/task state. ## Source -- Entry: `packages/coding-agent/src/tools/todo-write.ts` -- Model-facing prompt: `packages/coding-agent/src/prompts/tools/todo-write.md` +- Entry: `packages/coding-agent/src/tools/todo.ts` +- Model-facing prompt: `packages/coding-agent/src/prompts/tools/todo.md` - Key collaborators: - `packages/coding-agent/src/tools/index.ts` — registers tool, exposes session hooks, gates availability. - `packages/coding-agent/src/modes/controllers/event-controller.ts` — updates the visible todo UI on tool completion. @@ -59,10 +59,10 @@ The tool returns a single-shot `AgentToolResult`: - `TodoPhase`: `{ name: string, tasks: TodoItem[] }` - `TodoItem`: `{ content: string, status: "pending" | "in_progress" | "completed" | "abandoned", notes?: string[] }` -The TUI renderer (`todoWriteToolRenderer`) merges call and result into one transcript block, renders phases as a tree, shows note counts as superscripts, and renders the note bodies only for the current `in_progress` task. Collapsed transcript previews cap tree items at `PREVIEW_LIMITS.COLLAPSED_ITEMS` (`8`). +The TUI renderer (`todoToolRenderer`) merges call and result into one transcript block, renders phases as a tree, shows note counts as superscripts, and renders the note bodies only for the current `in_progress` task. Collapsed transcript previews cap tree items at `PREVIEW_LIMITS.COLLAPSED_ITEMS` (`8`). ## Flow -1. `TodoWriteTool.execute(...)` clones the current cached phases from `session.getTodoPhases?.() ?? []` (`packages/coding-agent/src/tools/todo-write.ts`). +1. `TodoTool.execute(...)` clones the current cached phases from `session.getTodoPhases?.() ?? []` (`packages/coding-agent/src/tools/todo.ts`). 2. `applyParams(...)` walks `params.ops` in order and applies each entry with `applyEntry(...)`. 3. Each op mutates the working phase array: - `initPhases(...)` rebuilds the list from scratch. @@ -77,7 +77,7 @@ The TUI renderer (`todoWriteToolRenderer`) merges call and result into one trans - if none are `in_progress`, the first `pending` task in phase/task order is auto-promoted to `in_progress`. 6. `execute(...)` stores the normalized phases with `session.setTodoPhases?.(...)` and reports `storage` as `"session"` when `session.getSessionFile()` exists, else `"memory"`. 7. `getCompletionTransitions(...)` compares the previous and updated phases; newly completed tasks are returned in `details.completedTasks`. -8. The agent runtime also watches `todo_write` tool results in `packages/coding-agent/src/session/agent-session.ts`; successful results refresh cached todos, failed results inject a hidden next-turn reminder telling the model that todo progress is not visible until it retries. +8. The agent runtime also watches `todo` tool results in `packages/coding-agent/src/session/agent-session.ts`; successful results refresh cached todos, failed results inject a hidden next-turn reminder telling the model that todo progress is not visible until it retries. 9. The event controller updates the visible todo UI from `result.details.phases` on success, or shows a warning on error (`packages/coding-agent/src/modes/controllers/event-controller.ts`). ## Modes / Variants @@ -113,16 +113,16 @@ The same file also exposes non-tool helpers used by `/todo`: - Mutates the session todo cache through `setTodoPhases`. - `storage` reports whether the session has a backing session file, but the tool does not append a custom session entry itself. - Successful tool-result messages carry `details.phases`; `getLatestTodoPhasesFromEntries(...)` can reconstruct state later from those transcript entries. - - Failed `todo_write` results cause `agent-session` to enqueue a hidden next-turn reminder (`customType: "todo-write-error-reminder"`). + - Failed `todo` results cause `agent-session` to enqueue a hidden next-turn reminder (`customType: "todo-error-reminder"`). - User-visible prompts / interactive UI - - Transcript block is rendered by `todoWriteToolRenderer` and merged with the call line. + - Transcript block is rendered by `todoToolRenderer` and merged with the call line. - `event-controller` updates the visible todo panel from successful results. - On error, `event-controller` shows `Todo update failed...`; the visible panel may stay stale until a later successful call. - Background work / cancellation - `AgentSession.setTodoPhases(...)` schedules auto-clear timers for `completed` / `abandoned` tasks via `tasks.todoClearDelay`. ## Limits & Caps -- `ops` array: `minItems: 1` (`todoWriteSchema`). +- `ops` array: `minItems: 1` (`todoSchema`). - `init.list[*].items`: `minItems: 1`. - `append.items`: `minItems: 1`. - Renderer collapsed preview: `PREVIEW_LIMITS.COLLAPSED_ITEMS = 8` (`packages/coding-agent/src/tools/render-utils.ts`). @@ -131,7 +131,7 @@ The same file also exposes non-tool helpers used by `/todo`: ## Errors - Ordinary bad op payloads are accumulated as human-readable strings in `errors`; the tool still returns the mutated state, but marks the result `isError: true`. -- Error strings come from the helpers in `packages/coding-agent/src/tools/todo-write.ts`, including: +- Error strings come from the helpers in `packages/coding-agent/src/tools/todo.ts`, including: - `Missing list for init operation` - `Missing task content` - `Task "..." not found` with an extra empty-list hint when applicable @@ -156,8 +156,8 @@ The same file also exposes non-tool helpers used by `/todo`: - `normalizeInProgressTask(...)` runs after the whole batch, not after each op. A single call can intentionally build an intermediate invalid state and rely on final normalization. - `storage: "session"` means the session has a session-file backing; it does not mean this tool wrote a durable custom entry. - Reload persistence differs by path: - - plain `todo_write` calls survive in transcript tool-result details; + - plain `todo` calls survive in transcript tool-result details; - `/todo` command edits additionally append `customType: "user_todo_edit"` entries and inject a visible-to-model `` developer message describing the manual edit. - On session resume, `AgentSession.#syncTodoPhasesFromBranch()` strips `completed` and `abandoned` tasks before restoring the cached list. The `/todo` command works around that by reading the latest transcript/custom-entry state so historical done/dropped tasks still appear to the user. - Tool availability is gated by `todo.enabled`, and the registry excludes it when `includeYield` is enabled (`packages/coding-agent/src/tools/index.ts`). -- Subagents do not inherit `todo_write`; `packages/coding-agent/src/task/executor.ts` filters it out as a parent-owned tool. +- Subagents do not inherit `todo`; `packages/coding-agent/src/task/executor.ts` filters it out as a parent-owned tool. diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index b58b21a1a..7ca6a37ec 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -436,7 +436,7 @@ export interface AgentTool>) => string | undefined); diff --git a/packages/ai/src/providers/cursor.ts b/packages/ai/src/providers/cursor.ts index 2bc805931..532dac027 100644 --- a/packages/ai/src/providers/cursor.ts +++ b/packages/ai/src/providers/cursor.ts @@ -612,7 +612,7 @@ export const streamCursor: StreamFunction<"cursor-agent"> = ( return stream; }; -type ToolCallState = ToolCall & { index: number; partialJson?: string; kind: "mcp" | "todo_write" }; +type ToolCallState = ToolCall & { index: number; partialJson?: string; kind: "mcp" | "todo" }; interface BlockState { currentTextBlock: (TextContent & { index: number }) | null; @@ -1872,7 +1872,7 @@ interface CursorUpdateTodosToolCall { updateTodosToolCall?: { args?: { todos?: CursorTodoItem[] } }; } -function buildTodoWriteArgs(toolCall: CursorUpdateTodosToolCall): { +function buildTodoArgs(toolCall: CursorUpdateTodosToolCall): { todos: Array<{ id?: string; content: string; activeForm: string; status: "pending" | "in_progress" | "completed" }>; } | null { const todos = toolCall.updateTodosToolCall?.args?.todos; @@ -2016,16 +2016,16 @@ function processInteractionUpdate( return; } - const todoArgs = buildTodoWriteArgs(toolCall); + const todoArgs = buildTodoArgs(toolCall); if (todoArgs) { const callId = update.message.value.callId || crypto.randomUUID(); const block: ToolCallState = { type: "toolCall", id: callId, - name: "todo_write", + name: "todo", arguments: todoArgs, index: output.content.length, - kind: "todo_write", + kind: "todo", }; output.content.push(block); state.setToolCall(block); @@ -2048,8 +2048,8 @@ function processInteractionUpdate( if (decodedArgs) { state.currentToolCall.arguments = decodedArgs; } - } else if (state.currentToolCall.kind === "todo_write" && toolCall) { - const todoArgs = buildTodoWriteArgs(toolCall); + } else if (state.currentToolCall.kind === "todo" && toolCall) { + const todoArgs = buildTodoArgs(toolCall); if (todoArgs) { state.currentToolCall.arguments = todoArgs; } @@ -2109,7 +2109,7 @@ function readCursorBlob(blobStore: Map, blobId: Uint8Array): return data; } -const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp", "todo_write"]); +const CURSOR_NATIVE_TOOL_NAMES = new Set(["bash", "read", "write", "delete", "ls", "grep", "lsp", "todo"]); function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[] { if (!tools || tools.length === 0) { diff --git a/packages/ai/test/anthropic-alignment.test.ts b/packages/ai/test/anthropic-alignment.test.ts index 2ea2ce33e..8fa8bb3e1 100644 --- a/packages/ai/test/anthropic-alignment.test.ts +++ b/packages/ai/test/anthropic-alignment.test.ts @@ -815,7 +815,7 @@ describe("Anthropic request fingerprint alignment", () => { required: ["requiredValue"], } as TJsonSchema, })), - ...(["write", "grep", "read", "task", "todo_write", "web_search", "ast_grep"] as const).map(name => ({ + ...(["write", "grep", "read", "task", "todo", "web_search", "ast_grep"] as const).map(name => ({ name, description: `${name} tool`, strict: true, diff --git a/packages/ai/test/anthropic-stream-timeout.test.ts b/packages/ai/test/anthropic-stream-timeout.test.ts index 0b14384b4..0d406d1f2 100644 --- a/packages/ai/test/anthropic-stream-timeout.test.ts +++ b/packages/ai/test/anthropic-stream-timeout.test.ts @@ -282,7 +282,7 @@ describe("anthropic first-event timeout retries", () => { content_block: { type: "tool_use", id: "toolu_stalled_todo", - name: "todo_write", + name: "todo", input: {}, }, }, @@ -308,7 +308,7 @@ describe("anthropic first-event timeout retries", () => { { type: "toolCall", id: "toolu_stalled_todo", - name: "todo_write", + name: "todo", arguments: {}, }, ]); diff --git a/packages/ai/test/duplicate-tool-results.test.ts b/packages/ai/test/duplicate-tool-results.test.ts index 6d3151d82..45d68ecbc 100644 --- a/packages/ai/test/duplicate-tool-results.test.ts +++ b/packages/ai/test/duplicate-tool-results.test.ts @@ -105,7 +105,7 @@ describe("Duplicate Tool Results Regression", () => { { type: "toolCall", id: toolCallId, - name: "todo_write", + name: "todo", arguments: { ops: [{ op: "update", id: "task-1", status: "completed" }] }, }, ], @@ -134,7 +134,7 @@ describe("Duplicate Tool Results Regression", () => { { role: "toolResult" as const, toolCallId, - toolName: "todo_write", + toolName: "todo", content: [{ type: "text" as const, text: "todo updated" }], isError: false, timestamp: Date.now(), diff --git a/packages/ai/test/google-tool-schema.test.ts b/packages/ai/test/google-tool-schema.test.ts index 69c425893..e82286879 100644 --- a/packages/ai/test/google-tool-schema.test.ts +++ b/packages/ai/test/google-tool-schema.test.ts @@ -217,7 +217,7 @@ describe("Cloud Code Assist Claude tool schema conversion", () => { }); expect(JSON.stringify(declaration.parameters)).not.toContain('"anyOf"'); }); - it("collapses mixed unions for todo_write-style nullable content fields", () => { + it("collapses mixed unions for todo-style nullable content fields", () => { const parameters = { type: "object", properties: { @@ -234,7 +234,7 @@ describe("Cloud Code Assist Claude tool schema conversion", () => { }, }, } as TJsonSchema; - const tools: Tool[] = [{ name: "todo_write", description: "Todo tool", parameters }]; + const tools: Tool[] = [{ name: "todo", description: "Todo tool", parameters }]; const model = createModel("claude-sonnet-4-5"); const declaration = convertTools(tools, model)?.[0]?.functionDeclarations[0] as Record; diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts index 12cd5e550..3e7bc30f9 100644 --- a/packages/ai/test/openai-codex-stream.test.ts +++ b/packages/ai/test/openai-codex-stream.test.ts @@ -106,7 +106,7 @@ function createNoProgressCodexSse(signal: AbortSignal | undefined): Response { type: "function_call", id: "fc_stalled", call_id: "call_stalled", - name: "todo_write", + name: "todo", arguments: "", }, }), @@ -333,7 +333,7 @@ describe("openai-codex streaming", () => { { type: "toolCall", id: "call_stalled|fc_stalled", - name: "todo_write", + name: "todo", arguments: {}, partialJson: "", }, @@ -2045,7 +2045,7 @@ describe("openai-codex streaming", () => { type: "function_call", id: "fc_ws_stalled", call_id: "call_ws_stalled", - name: "todo_write", + name: "todo", arguments: "", }, }); @@ -2055,7 +2055,7 @@ describe("openai-codex streaming", () => { type: "function_call", id: "fc_ws_stalled", call_id: "call_ws_stalled", - name: "todo_write", + name: "todo", arguments: "{}", }, }); @@ -2090,7 +2090,7 @@ describe("openai-codex streaming", () => { expect.objectContaining({ type: "toolCall", id: "call_ws_stalled|fc_ws_stalled", - name: "todo_write", + name: "todo", arguments: {}, }), ]); diff --git a/packages/ai/test/openai-first-event-timeout.test.ts b/packages/ai/test/openai-first-event-timeout.test.ts index 123083df1..ead5e1eed 100644 --- a/packages/ai/test/openai-first-event-timeout.test.ts +++ b/packages/ai/test/openai-first-event-timeout.test.ts @@ -116,7 +116,7 @@ function createNoProgressOpenAIResponsesStream(signal: AbortSignal | undefined): type: "function_call", id: "fc_stalled", call_id: "call_stalled", - name: "todo_write", + name: "todo", arguments: "", status: "in_progress", }, @@ -407,7 +407,7 @@ describe("OpenAI-family first-event timeouts", () => { { type: "toolCall", id: "call_stalled|fc_stalled", - name: "todo_write", + name: "todo", arguments: {}, partialJson: "", }, diff --git a/packages/ai/test/tool-argument-coercion.test.ts b/packages/ai/test/tool-argument-coercion.test.ts index b01bf56b8..224159d5b 100644 --- a/packages/ai/test/tool-argument-coercion.test.ts +++ b/packages/ai/test/tool-argument-coercion.test.ts @@ -743,7 +743,7 @@ describe("Tool argument coercion", () => { }); it("parses JSON-stringified array containing raw newlines inside string values", () => { const tool: Tool = { - name: "todo_write_like", + name: "todo_like", description: "", parameters: z.object({ phases: z.array( @@ -769,7 +769,7 @@ describe("Tool argument coercion", () => { const toolCall: ToolCall = { type: "toolCall", id: "call-rawnl", - name: "todo_write_like", + name: "todo_like", arguments: { phases: stringifiedPhases }, }; diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 141adac8f..fc179fb66 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -37,7 +37,7 @@ ### Changed -- Changed the `todo-write` prompt to require initializing every item from a user-supplied multi-step plan as an individual todo task before execution +- Changed the `todo` prompt to require initializing every item from a user-supplied multi-step plan as an individual todo task before execution - Changed context compaction (prune/shake) to protect reads of the active plan file the same way it already protects `skill://` reads, so the plan stays intact through automatic and manual compaction. Both the canonical `local://PLAN.md` alias and the session's current plan reference path (e.g. a titled `local://.md` after approval) are kept, tolerating read selectors and `local:/` scheme spelling. ## [15.8.2] - 2026-06-03 @@ -273,7 +273,7 @@ - Changed the `task` tool's streaming call preview to list each dispatched agent's `id` and UI description as a tree instead of a bare `N agents` count, so the individual agents are visible while the tool-call arguments are still streaming. The collapsed view caps at 12 entries (`… N more agents`); the expanded view shows all. - Changed Mnemopi `recall` tool output to include memory ids for explicit recall results so agents can target `memory_edit`; auto-injected memory context and `reflect` remain id-free. - Changed the system prompt to advertise `memory://root` only when the local memory backend is active. -- Changed `todo_write` result rendering to animate completed items in place: the checkbox flips checked first, then the strikethrough reveals across the task text. +- Changed `todo` result rendering to animate completed items in place: the checkbox flips checked first, then the strikethrough reveals across the task text. ### Fixed @@ -353,7 +353,7 @@ ### Changed -- Changed the sticky `Todos` panel above the editor to advance as tasks close, instead of pinning to the first 5 tasks of the active phase. `selectStickyTodoWindow` now shows up to 5 open (pending / in_progress) tasks in original phase order and reports the count of remaining open tasks for the `+N more` hint, so every `todo_write` flip produces a visible row shift. Closed-phase tail falls back to the last 5 tasks (with the `+N more` line suppressed) until `getActivePhase` walks to the next phase. +- Changed the sticky `Todos` panel above the editor to advance as tasks close, instead of pinning to the first 5 tasks of the active phase. `selectStickyTodoWindow` now shows up to 5 open (pending / in_progress) tasks in original phase order and reports the count of remaining open tasks for the `+N more` hint, so every `todo` flip produces a visible row shift. Closed-phase tail falls back to the last 5 tasks (with the `+N more` line suppressed) until `getActivePhase` walks to the next phase. - Linked the sticky `Todos` panel to the live `SessionObserverRegistry` so pending todos that have an in-flight subagent doing their work light up green with an animated spinner — the same `theme.spinnerFrames` ("status" preset) the `task` tool uses for its agent rows — instead of staying greyed out as if nothing is happening. A new exported `todoMatchesAnyDescription(content, descriptions)` does case- and whitespace-insensitive equality first with a 6-char minimum-overlap substring fallback in either direction, so "Sonnet #2: shallow bug scan" and a subagent description of "Sonnet #2" still link up. Completed todos now render with `theme.status.success` (✔ / `\uf00c` / `[ok]` per symbol preset, still wrapped in the `success` colour so themed palettes can keep their purple/green/whatever) and in_progress rows render with `theme.status.running`, matching the `task` tool's icon vocabulary. The spinner interval only ticks while at least one visible open todo has a matched active subagent, and self-stops once subagents finish, so plain in_progress todos do not animate forever in the absence of subagent activity. - Extracted the top-level CLI command table from `src/cli.ts` into a side-effect-free `src/cli-commands.ts` so test code can introspect the registered subcommands without triggering the entrypoint's top-level await. @@ -1754,9 +1754,9 @@ ### Breaking Changes -- `todo_write`: renamed `replace` op to `init` and reshaped its input to `list: [{phase: string, items: string[]}]`. Tasks no longer accept a `status` field; all start `pending` and the first auto-promotes to `in_progress`. The `append` op's `items` is now `string[]` (was `{id, label}[]`) -- `todo_write`: removed the synthetic `task-N` / `phase-N` ids — task identity is now its `content` and phase identity is its `name`. The `task` field on `start`/`done`/`drop`/`note` and the `phase` field on `done`/`drop`/`rm`/`append` take those values directly -- `todo_write`: phase names no longer accept a numeric/roman prefix (`I.`, `1.`, `Phase 1:`, …). The renderer numbers phases visually (Ⅰ. Ⅱ. Ⅲ. …) and the model-facing state stores the bare noun phrase +- `todo`: renamed `replace` op to `init` and reshaped its input to `list: [{phase: string, items: string[]}]`. Tasks no longer accept a `status` field; all start `pending` and the first auto-promotes to `in_progress`. The `append` op's `items` is now `string[]` (was `{id, label}[]`) +- `todo`: removed the synthetic `task-N` / `phase-N` ids — task identity is now its `content` and phase identity is its `name`. The `task` field on `start`/`done`/`drop`/`note` and the `phase` field on `done`/`drop`/`rm`/`append` take those values directly +- `todo`: phase names no longer accept a numeric/roman prefix (`I.`, `1.`, `Phase 1:`, …). The renderer numbers phases visually (Ⅰ. Ⅱ. Ⅲ. …) and the model-facing state stores the bare noun phrase ### Changed @@ -1982,7 +1982,7 @@ ### Added -- Added `note` to todo-write operations so you can append follow-up text notes to a task via `op: "note"` and `text` +- Added `note` to todo operations so you can append follow-up text notes to a task via `op: "note"` and `text` - Added markdown note-block support to `/todo export` and `/todo import` so task notes are written as blockquote lines and reloaded with the todo list - Added `/todo export <path>` to write the current todo list as Markdown to a file, defaulting to `TODO.md` when no path is provided - Added `/todo import <path>` to replace the current todo list from a Markdown file, defaulting to `TODO.md` when no path is provided @@ -2046,7 +2046,7 @@ - Removed multi-pattern array input from `ast_grep` by changing `pat` to a single pattern string, so call sites using `pat: [...]` must be updated to send one query per invocation - Removed `lang`, `glob`, and `sel` options from `ast_edit` and `ast_grep`, and moved those behaviors into the required `path` argument - Required `path` for `ast_edit` and `ast_grep`, so invocations that relied on implicit repo-root searching are no longer valid -- Changed `todo_write` from multi-field verb payloads to an ordered array of flat operations, while retaining `replace` for harness bootstrap compatibility +- Changed `todo` from multi-field verb payloads to an ordered array of flat operations, while retaining `replace` for harness bootstrap compatibility - Renamed atom edit operations from `before` and `after` to `pre` and `post`, so existing `atom` payloads using the old operation keys must be updated - Changed the hashline anchor format from `LINE#ID:content` to `LINEID:content` (no `#` separator, colon between anchor and content, no padding on line numbers); expanded the bigram alphabet from 40 hand-picked English bigrams to the full 647 single-token 2-letter bigrams — invalidates every previously captured `LINE#ID` reference - Renamed the subagent completion contract from `submit_result` to `yield`, so subagent sessions must now finish with the `yield` tool and the `requireYieldTool` option; `submit_result`/`requireSubmitResultTool` and old completion calls are no longer recognized @@ -2065,10 +2065,10 @@ - Updated `atom` and `hashline` edit anchor validation to auto-rebase a stale anchor within ±2 lines when the same hash matches a unique nearby line, continuing the edit with a warning instead of immediate failure - Changed bash command output labels from `[full result: artifact://…]` to `[raw output: artifact://…]` for artifact references produced from large command output -- Changed `todo_write` `done`, `rm`, and `drop` operations to target all tasks when neither `task` nor `phase` is provided, and made `append` create the target phase automatically when missing +- Changed `todo` `done`, `rm`, and `drop` operations to target all tasks when neither `task` nor `phase` is provided, and made `append` create the target phase automatically when missing - Updated `ast_edit` and `ast_grep` to pass file-selection intent through `path` (including inline globs and comma/space-separated path lists) instead of separate `glob` filters - Changed `ast_grep` pagination API from `offset` to `skip` -- Flattened `todo_write` operation arguments to `{ op, task?, phase?, items? }[]` and removed task details from the persisted todo shape +- Flattened `todo` operation arguments to `{ op, task?, phase?, items? }[]` and removed task details from the persisted todo shape - Changed `grep` truncation output to report `Result limit reached; narrow path.` and label match/result caps as `first N` - Changed JSON tree output to truncate inline argument pairs by available width and add an ellipsis when values no longer fit in the display - Changed JSON tree rendering to hide harness-internal `intent` and `__partialJson` fields from top-level tool output @@ -2091,12 +2091,12 @@ - Removed line-range support from `atom` mode selectors, including `loc` values like `160sr-170ab`, so edits must target a single anchor (`160sr`, `^`, or `$`) per entry - Removed the atom `del` verb and now require anchored-line deletion to be requested with `set: []` -- Removed `todo_write` task details and the `add_notes` operation +- Removed `todo` task details and the `add_notes` operation ### Fixed - Improved no-op edit diagnostics for `atom` and `hashline` operations so edits that leave content unchanged now fail with contextual details (edit index, locator, and reason), including guidance for `replace_range` no-op cases -- Wrapped `todo_write` operations in an `ops` object so Codex/OpenAI function schemas always use a JSON Schema object. +- Wrapped `todo` operations in an `ops` object so Codex/OpenAI function schemas always use a JSON Schema object. - Fixed JSON tree rendering for tool arguments by excluding injected internal keys from displayed root records - Printed assistant `errorMessage` text in print mode output to stderr so message-level errors are visible during non-interactive runs - Displayed assistant `errorMessage` text in the assistant message component for completed tool responses with non-terminal stop reasons @@ -2197,14 +2197,14 @@ ### Breaking Changes -- Replaced the legacy `todo_write` `ops`-based API (`replace`, `update`, `add_task`, and `remove_task`) with direct top-level fields, requiring migration of any callers using the old request shape -- Removed in-place updates to existing task `content`, `details`, and `notes` via `todo_write`; note changes now append through `add_notes` -- Phased task definitions in `todo_write` now reject `notes` on initial creation, so notes must be added later with `add_notes` +- Replaced the legacy `todo` `ops`-based API (`replace`, `update`, `add_task`, and `remove_task`) with direct top-level fields, requiring migration of any callers using the old request shape +- Removed in-place updates to existing task `content`, `details`, and `notes` via `todo`; note changes now append through `add_notes` +- Phased task definitions in `todo` now reject `notes` on initial creation, so notes must be added later with `add_notes` ### Added -- Added `complete`, `start`, `abandon`, `remove`, `add_notes`, and `add_tasks` parameters to `todo_write` so callers can complete, jump to, drop, and annotate tasks without op wrappers -- Added direct `add_phase` support as a top-level argument for inserting a new phase in `todo_write` +- Added `complete`, `start`, `abandon`, `remove`, `add_notes`, and `add_tasks` parameters to `todo` so callers can complete, jump to, drop, and annotate tasks without op wrappers +- Added direct `add_phase` support as a top-level argument for inserting a new phase in `todo` - Added `task.simple` with `default`, `schema-free`, and `independent` modes so the task tool can disable task-call `schema` and shared `context` inputs while preserving agent-defined and inherited subagent schemas ### Changed @@ -4029,7 +4029,7 @@ - Fixed workspace symbol search to query all configured LSP servers and filter out non-matching results - Fixed `references`/`rename`/`hover` symbol targeting to error when `symbol` is missing on the line or `occurrence` is out of bounds - Fixed `reload` without a file to reload all active configured language servers instead of only the first server -- Fixed `todo_write` task normalization to auto-activate the first remaining task and include explicit remaining-items output in tool results, removing the need for an immediate follow-up start update +- Fixed `todo` task normalization to auto-activate the first remaining task and include explicit remaining-items output in tool results, removing the need for an immediate follow-up start update ## [13.3.7] - 2026-02-27 @@ -4228,7 +4228,7 @@ - Changed todo state management from file-based (`todos.json`) to in-memory session cache for improved performance and consistency - Changed todo phases to sync from session branch history when branching or rewriting entries -- Changed `TodoWriteTool` to update session cache instead of writing to disk, with automatic persistence through session entries +- Changed `TodoTool` to update session cache instead of writing to disk, with automatic persistence through session entries - Changed XML tag from `<swarm-context>` to `<context>` in subagent prompts and task rendering - Changed system reminder XML tags from underscore to kebab-case format (`<system-reminder>`) - Changed plan storage from `plan://` protocol to `local://PLAN.md` for draft plans and `local://<title>.md` for finalized approved plans @@ -5289,7 +5289,7 @@ ### Fixed - Fixed TUI crash when ask tool renders long user input exceeding terminal width by using Text component for word wrapping instead of raw line output -- Fixed TUI crash when todo_write tool renders long todo content exceeding terminal width by using Text component for word wrapping instead of truncation +- Fixed TUI crash when todo tool renders long todo content exceeding terminal width by using Text component for word wrapping instead of truncation ## [11.5.0] - 2026-02-06 @@ -5533,7 +5533,7 @@ - Added new subcommands to help text: `commit` for AI-assisted git commits, `stats` for AI usage statistics dashboard, and `jupyter` for managing the shared Jupyter gateway - Added `grep` subcommand to help text for testing the grep tool - Added `browser` tool documentation for browser automation using Puppeteer -- Added `todo_write` tool documentation for managing todo and task lists +- Added `todo` tool documentation for managing todo and task lists - Added documentation for additional LLM provider API keys (Groq, Cerebras, xAI, OpenRouter, Mistral, z.ai, MiniMax, OpenCode, Cursor, Vercel AI Gateway) in environment variables reference - Added documentation for cloud provider configuration (AWS Bedrock, Google Vertex AI) in environment variables reference - Added documentation for search provider API keys (Perplexity, Anthropic Search) in environment variables reference @@ -5685,7 +5685,7 @@ - Tightened `ask` tool conditions to require multiple approaches with significantly different tradeoffs before prompting user - Strengthened `ask` tool guidance to default to action and only ask when genuinely blocked by decisions with materially different outcomes - Changed refactor workflow to automatically remove now-unused elements and note removals instead of asking for confirmation -- Enforced exclusive concurrency mode for all file-modifying tools (edit, write, bash, python, ssh, todo-write) to prevent concurrent execution conflicts +- Enforced exclusive concurrency mode for all file-modifying tools (edit, write, bash, python, ssh, todo) to prevent concurrent execution conflicts - Updated `ask` tool guidance to prioritize proactive problem-solving and default to action, asking only when truly blocked by decisions that materially change scope or behavior - Changed Python kernel initialization to require shared gateway mode; local gateway startup has been removed - Changed shared gateway error handling to retry on server errors (5xx status codes) before failing @@ -5726,7 +5726,7 @@ - Added `find.enabled`, `grep.enabled`, `ls.enabled`, `notebook.enabled`, `fetch.enabled`, `web_search.enabled`, `lsp.enabled`, and `calc.enabled` settings to control availability of individual tools - Added conditional tool documentation in system prompt that dynamically lists only enabled specialized tools -- Added `todos.enabled` setting to control availability of the todo_write tool for task tracking +- Added `todos.enabled` setting to control availability of the todo tool for task tracking - Added `tools` field to agent frontmatter for declaring agent-specific tool capabilities ### Changed @@ -6290,10 +6290,10 @@ - Changed Web Search result rendering to use renderOutputBlock with answer, sources, related questions, and metadata sections - Changed Find, Grep, and Ls tools to use renderFileList and renderTreeList for consistent file/item listing - Changed Calculator tool result rendering to use renderTreeList for result item display -- Changed Notebook and TodoWrite tools to use new TUI rendering components for consistent output format +- Changed Notebook and Todo tools to use new TUI rendering components for consistent output format - Refactored render-utils to move tree-related utilities to TUI module (getTreeBranch, getTreeContinuePrefix) - Changed import organization in sdk.ts for consistency -- Changed tool result rendering to merge call and result displays, showing tool arguments (command, pattern, query, path) in result headers for Bash, Calculator, Fetch, Find, Grep, Ls, LSP, Notebook, Read, SSH, TodoWrite, Web Search, and Write tools +- Changed tool result rendering to merge call and result displays, showing tool arguments (command, pattern, query, path) in result headers for Bash, Calculator, Fetch, Find, Grep, Ls, LSP, Notebook, Read, SSH, Todo, Web Search, and Write tools - Changed Read tool title to display line range when offset or limit arguments are provided - Changed worker instantiation to use direct URL import instead of pre-bundled worker files - Changed `omp commit` to use agentic mode by default with tool-based git inspection @@ -6307,7 +6307,7 @@ - Changed Calculator tool result display to show both expression and output (e.g., `2+2 = 4`) instead of just the result - Changed Python tool output to group status information under a labeled section for clearer organization - Changed SSH tool output to apply consistent styling to non-ANSI output lines -- Changed Todo Write tool to respect expanded/collapsed state and use standard preview limits +- Changed Todo tool to respect expanded/collapsed state and use standard preview limits - Changed Web Search related questions to respect expanded/collapsed state instead of always showing all items - Changed empty and error state rendering across multiple tools (Find, Grep, Ls, Notebook, Calculator, Ask) to include consistent status headers - Changed split commit to support hunk selectors (all, indices, or line ranges) instead of whole-file staging @@ -6411,10 +6411,10 @@ - Enhanced bash and python executors to save full output as artifacts when truncated - Improved abort signal handling across <caution>ith consistent ToolAbortError - Renamed task parameter from `vars` to `args` throughout task tool interface and updated template rendering to support built-in `{{id}}` and `{{description}}` placeholders -- Simplified todo-write tool by removing active_form parameter, using single content field for task descriptions +- Simplified todo tool by removing active_form parameter, using single content field for task descriptions - Updated system prompt structure with `<important>` and `<avoid>` tags, clearer critical sections, and standardized whitespace handling - Renamed web_fetch tool to fetch and removed internal URL handling (use read tool instead) -- Standardized tool parameter names from camelCase to snake_case across edit, grep, python, and todo-write tools +- Standardized tool parameter names from camelCase to snake_case across edit, grep, python, and todo tools - Unified timeout parameters across all tools with auto-conversion from milliseconds and reasonable clamping (1s-3600s for bash/ssh, 1s-600s for python/gemini-image) - Simplified web-search tool by removing advanced parameters (`max_tokens`, `model`, `search_domain_filter`, `search_context_size`, `return_related_questions`) and using `recency` instead of `search_recency_filter` - Restructured tool documentation with standardized `<instruction>`, `<output>`, `<critical>`, and `<avoid>` sections across all 18 tools @@ -7023,11 +7023,11 @@ ### Added -- Added `todo_write` tool for creating and managing structured task lists during coding sessions +- Added `todo` tool for creating and managing structured task lists during coding sessions - Added persistent todo panel above the editor that displays task progress - Added `Ctrl+T` keybinding to toggle todo list expansion - Added grouped display for consecutive Read tool calls, showing multiple file reads in a compact tree view -- Added `todo_write` tool and persistent todo panel above the editor +- Added `todo` tool and persistent todo panel above the editor ### Changed diff --git a/packages/coding-agent/DEVELOPMENT.md b/packages/coding-agent/DEVELOPMENT.md index 849536d79..731434b48 100644 --- a/packages/coding-agent/DEVELOPMENT.md +++ b/packages/coding-agent/DEVELOPMENT.md @@ -396,7 +396,7 @@ A `ToolFactory` is `(session: ToolSession) => Tool | null | Promise<Tool | null> 4. Computes effective gating (`isToolAllowed`) from settings and runtime state: - feature toggles (`find.enabled`, `grep.enabled`, etc.) - recursion guard for `task` (`task.maxRecursionDepth` vs `session.taskDepth`) - - yield mode (`requireYieldTool`) and `todo_write` suppression + - yield mode (`requireYieldTool`) and `todo` suppression 5. Instantiates selected tools in parallel with `Promise.all`, records slow factory timings when `PI_TIMING=1`, and wraps results with `wrapToolWithMetaNotice`. 6. Includes `resolve` unconditionally so plan mode and deferred preview/apply workflows always have it available. @@ -923,7 +923,7 @@ What _is_ isolated is execution context and artifacts, not process memory: - Removes `task` when max recursion depth is reached (`task.maxRecursionDepth`). - Expands legacy `exec` alias into `eval` when any eval backend is enabled, and always includes `bash`. - Forces `requireYieldTool: true` in `createAgentSession(...)`. -- Filters parent-owned tools out of child tools (`todo_write` is removed). +- Filters parent-owned tools out of child tools (`todo` is removed). If parent MCP connections exist, executor creates in-process MCP proxy tools with `createMCPProxyTools(...)` so children reuse parent MCP connectivity rather than creating independent MCP sessions. diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index dcef3d6da..cefb84942 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -313,7 +313,7 @@ ${chalk.bold("Available Tools (default-enabled unless noted):")} inspect_image - Analyze images with a vision model browser - Browser automation (Puppeteer) task - Launch sub-agents for parallel tasks - todo_write - Manage todo/task lists + todo - Manage todo/task lists web_search - Search the web ask - Ask user questions (interactive mode only) diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index f161af965..e379ba7ce 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -2122,7 +2122,7 @@ export const SETTINGS_SCHEMA = { "todo.enabled": { type: "boolean", default: true, - ui: { tab: "tools", label: "Todos", description: "Enable the todo_write tool for task tracking" }, + ui: { tab: "tools", label: "Todos", description: "Enable the todo tool for task tracking" }, }, "todo.reminders": { diff --git a/packages/coding-agent/src/export/html/template.generated.ts b/packages/coding-agent/src/export/html/template.generated.ts index a4cad2310..7e4a049be 100644 --- a/packages/coding-agent/src/export/html/template.generated.ts +++ b/packages/coding-agent/src/export/html/template.generated.ts @@ -1,2 +1,2 @@ // Auto-generated by scripts/generate-template.ts - DO NOT EDIT -export const TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Session Export\n \n \n\n\n \n
\n
\n \n
\n
\n
\n
\n
\n
\n \"\"\n
\n
\n\n \n \n \n \n\n\n"; +export const TEMPLATE = "\n\n\n \n \n Session Export\n \n \n\n\n \n
\n
\n \n
\n
\n
\n
\n
\n
\n \"\"\n
\n
\n\n \n \n \n \n\n\n"; diff --git a/packages/coding-agent/src/export/html/template.js b/packages/coding-agent/src/export/html/template.js index 0faea4a1d..9e237a24a 100644 --- a/packages/coding-agent/src/export/html/template.js +++ b/packages/coding-agent/src/export/html/template.js @@ -981,8 +981,8 @@ return out; } - function renderTodoWrite(name, args, result, ctx) { - let html = toolHead('todo_write'); + function renderTodo(name, args, result, ctx) { + let html = toolHead('todo'); const ops = Array.isArray(args.ops) ? args.ops : null; if (ops) { html += '
'; @@ -1520,7 +1520,7 @@ search: renderSearch, find: renderFind, lsp: renderLsp, - todo_write: renderTodoWrite, + todo: renderTodo, task: renderTask, web_search: renderWebSearch, fetch: renderFetch, diff --git a/packages/coding-agent/src/extensibility/custom-tools/types.ts b/packages/coding-agent/src/extensibility/custom-tools/types.ts index 3115eae8c..d1f2cb73b 100644 --- a/packages/coding-agent/src/extensibility/custom-tools/types.ts +++ b/packages/coding-agent/src/extensibility/custom-tools/types.ts @@ -21,7 +21,7 @@ import type { ExecOptions, ExecResult } from "../../exec/exec"; import type { HookUIContext } from "../../extensibility/hooks/types"; import type { Theme } from "../../modes/theme/theme"; import type { ReadonlySessionManager } from "../../session/session-manager"; -import type { TodoItem } from "../../tools/todo-write"; +import type { TodoItem } from "../../tools/todo"; /** Alias for clarity */ export type CustomToolUIContext = HookUIContext; diff --git a/packages/coding-agent/src/extensibility/shared-events.ts b/packages/coding-agent/src/extensibility/shared-events.ts index 0ee574ef4..cefc1ea94 100644 --- a/packages/coding-agent/src/extensibility/shared-events.ts +++ b/packages/coding-agent/src/extensibility/shared-events.ts @@ -18,7 +18,7 @@ import type { ImageContent, TextContent, ToolResultMessage } from "@oh-my-pi/pi- import type { Rule } from "../capability/rule"; import type { Goal, GoalModeState } from "../goals/state"; import type { BranchSummaryEntry, CompactionEntry, SessionEntry } from "../session/session-manager"; -import type { TodoItem } from "../tools/todo-write"; +import type { TodoItem } from "../tools/todo"; // ============================================================================ // Session Events diff --git a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts index bbad196c3..9dafabe29 100644 --- a/packages/coding-agent/src/modes/acp/acp-event-mapper.ts +++ b/packages/coding-agent/src/modes/acp/acp-event-mapper.ts @@ -8,7 +8,7 @@ import type { } from "@agentclientprotocol/sdk"; import type { AgentSessionEvent } from "../../session/agent-session"; import { resolveToCwd } from "../../tools/path-utils"; -import type { TodoStatus } from "../../tools/todo-write"; +import type { TodoStatus } from "../../tools/todo"; interface MessageProgress { textEmitted: boolean; @@ -148,7 +148,7 @@ export function mapToolKind(toolName: string): ToolKind { return "search"; case "web_search": return "fetch"; - case "todo_write": + case "todo": return "think"; default: return "other"; @@ -215,7 +215,7 @@ export function mapAgentSessionEventToAcpSessionUpdates( update.locations = locations; } const notifications = [toSessionNotification(sessionId, update)]; - const planUpdate = mapTodoWriteResultToPlanUpdate(event); + const planUpdate = mapTodoResultToPlanUpdate(event); if (planUpdate) { notifications.push(toSessionNotification(sessionId, planUpdate)); } @@ -336,13 +336,13 @@ function mapTodoStatus(status: TodoStatus): "pending" | "in_progress" | "complet return todoStatusMap[status]; } -function mapTodoWriteResultToPlanUpdate( +function mapTodoResultToPlanUpdate( event: Extract, ): SessionUpdate | undefined { - if (event.toolName !== "todo_write" || event.isError) { + if (event.toolName !== "todo" || event.isError) { return undefined; } - const phases = extractTodoWritePhases(event.result); + const phases = extractTodoPhases(event.result); if (!Array.isArray(phases)) { return undefined; } @@ -356,7 +356,7 @@ function mapTodoWriteResultToPlanUpdate( }; } -function extractTodoWritePhases(result: unknown): unknown { +function extractTodoPhases(result: unknown): unknown { if (typeof result !== "object" || result === null || !("details" in result)) { return undefined; } diff --git a/packages/coding-agent/src/modes/components/todo-reminder.ts b/packages/coding-agent/src/modes/components/todo-reminder.ts index c99e2b28b..0a536d73d 100644 --- a/packages/coding-agent/src/modes/components/todo-reminder.ts +++ b/packages/coding-agent/src/modes/components/todo-reminder.ts @@ -1,6 +1,6 @@ import { Box, Container, Spacer, Text } from "@oh-my-pi/pi-tui"; import { theme } from "../../modes/theme/theme"; -import type { TodoItem } from "../../tools/todo-write"; +import type { TodoItem } from "../../tools/todo"; /** * Component that renders a todo completion reminder notification. diff --git a/packages/coding-agent/src/modes/components/tool-execution.ts b/packages/coding-agent/src/modes/components/tool-execution.ts index f04fcf8a5..939d2d48c 100644 --- a/packages/coding-agent/src/modes/components/tool-execution.ts +++ b/packages/coding-agent/src/modes/components/tool-execution.ts @@ -32,7 +32,7 @@ import { } from "../../tools/json-tree"; import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils"; import { toolRenderers } from "../../tools/renderers"; -import { TODO_WRITE_STRIKE_TOTAL_FRAMES } from "../../tools/todo-write"; +import { TODO_STRIKE_TOTAL_FRAMES } from "../../tools/todo"; import { renderStatusLine } from "../../tui"; import { sanitizeWithOptionalSixelPassthrough } from "../../utils/sixel"; import { renderDiff } from "./diff"; @@ -452,7 +452,7 @@ export class ToolExecutionComponent extends Container { } #updateTodoStrikeAnimation(): void { - if (this.#toolName !== "todo_write" || this.#isPartial || this.#result?.isError) { + if (this.#toolName !== "todo" || this.#isPartial || this.#result?.isError) { this.#stopTodoStrikeAnimation(); return; } @@ -467,7 +467,7 @@ export class ToolExecutionComponent extends Container { this.#renderState.spinnerFrame = 0; this.#todoStrikeInterval = setInterval(() => { const nextFrame = (this.#spinnerFrame ?? 0) + 1; - if (nextFrame > TODO_WRITE_STRIKE_TOTAL_FRAMES) { + if (nextFrame > TODO_STRIKE_TOTAL_FRAMES) { this.#stopTodoStrikeAnimation(); } else { this.#spinnerFrame = nextFrame; diff --git a/packages/coding-agent/src/modes/controllers/event-controller.ts b/packages/coding-agent/src/modes/controllers/event-controller.ts index 257aa498f..b7f49a071 100644 --- a/packages/coding-agent/src/modes/controllers/event-controller.ts +++ b/packages/coding-agent/src/modes/controllers/event-controller.ts @@ -587,18 +587,18 @@ export class EventController { this.ctx.ui.requestRender(); } } - // Update todo display when todo_write tool completes - if (event.toolName === "todo_write" && !event.isError) { + // Update todo display when todo tool completes + if (event.toolName === "todo" && !event.isError) { const details = event.result.details as { phases?: TodoPhase[] } | undefined; if (details?.phases) { this.ctx.setTodos(details.phases); } - } else if (event.toolName === "todo_write" && event.isError) { + } else if (event.toolName === "todo" && event.isError) { const textContent = event.result.content.find( (content: { type: string; text?: string }) => content.type === "text", )?.text; this.ctx.showWarning( - `Todo update failed${textContent ? `: ${textContent}` : ". Progress may be stale until todo_write succeeds."}`, + `Todo update failed${textContent ? `: ${textContent}` : ". Progress may be stale until todo succeeds."}`, ); } if (event.toolName === "resolve" && !event.isError) { diff --git a/packages/coding-agent/src/modes/controllers/todo-command-controller.ts b/packages/coding-agent/src/modes/controllers/todo-command-controller.ts index b5decbc2f..b105905a0 100644 --- a/packages/coding-agent/src/modes/controllers/todo-command-controller.ts +++ b/packages/coding-agent/src/modes/controllers/todo-command-controller.ts @@ -8,7 +8,7 @@ import { type TodoItem, type TodoPhase, USER_TODO_EDIT_CUSTOM_TYPE, -} from "../../tools/todo-write"; +} from "../../tools/todo"; import { copyToClipboard } from "../../utils/clipboard"; import { getEditorCommand, openInEditor } from "../../utils/external-editor"; import type { InteractiveModeContext } from "../types"; diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index 094e9c1d3..0406fe447 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -86,7 +86,7 @@ import type { LspStartupServerInfo } from "../tools"; import { normalizeLocalScheme } from "../tools/path-utils"; import { setAutoQaConsentHandler } from "../tools/report-tool-issue"; import { type ResolveToolDetails, runResolveInvocation } from "../tools/resolve"; -import { formatPhaseDisplayName, selectStickyTodoWindow, todoMatchesAnyDescription } from "../tools/todo-write"; +import { formatPhaseDisplayName, selectStickyTodoWindow, todoMatchesAnyDescription } from "../tools/todo"; import { ToolError } from "../tools/tool-errors"; import type { EventBus } from "../utils/event-bus"; import { getEditorCommand, openInEditor } from "../utils/external-editor"; @@ -1067,7 +1067,7 @@ export class InteractiveMode implements InteractiveModeContext { * Auto-complete any pending/in_progress todo whose content matches a * subagent that has finished successfully. Fires on every observer * `onChange` so the visual state stays in sync with subagent lifecycle - * without requiring the agent to issue a follow-up `todo_write`. Failed + * without requiring the agent to issue a follow-up `todo`. Failed * and aborted subagents are intentionally NOT auto-completed — those * stay open so the user (or the next agent turn) can decide what to do. * diff --git a/packages/coding-agent/src/modes/rpc/rpc-types.ts b/packages/coding-agent/src/modes/rpc/rpc-types.ts index 568611758..5ff67a664 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-types.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-types.ts @@ -10,7 +10,7 @@ import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai"; import type { BashResult } from "../../exec/bash-executor"; import type { ContextUsage } from "../../extensibility/extensions/types"; import type { SessionStats } from "../../session/agent-session"; -import type { TodoPhase } from "../../tools/todo-write"; +import type { TodoPhase } from "../../tools/todo"; // ============================================================================ // RPC Commands (stdin) diff --git a/packages/coding-agent/src/prompts/goals/goal-continuation.md b/packages/coding-agent/src/prompts/goals/goal-continuation.md index 771fb3649..e8848393a 100644 --- a/packages/coding-agent/src/prompts/goals/goal-continuation.md +++ b/packages/coding-agent/src/prompts/goals/goal-continuation.md @@ -16,7 +16,7 @@ This is an autonomous continuation. The objective persists across turns; do not Before calling `goal({op:"complete"})`, you MUST perform a completion audit against the current repo state: -1. **Restate the objective as concrete deliverables.** What files, behaviors, tests, gates, or artifacts must exist for the objective to be true? Write them down (todo_write, or in your reasoning). +1. **Restate the objective as concrete deliverables.** What files, behaviors, tests, gates, or artifacts must exist for the objective to be true? Write them down (todo, or in your reasoning). 2. **Map each deliverable to evidence.** For every requirement, identify the authoritative source that would prove it: a file's contents, a command's output, a test's pass status, a PR/issue state. 3. **Inspect the actual current state.** Read the files. Run the commands. Check the tests. Do not rely on memory of earlier work in this session — the repo may have changed. 4. **Match verification scope to claim scope.** A narrow check (one file passes its unit test) does not prove a broad claim (the feature works end-to-end). diff --git a/packages/coding-agent/src/prompts/system/eager-todo.md b/packages/coding-agent/src/prompts/system/eager-todo.md index d5662ade5..0d0a5483d 100644 --- a/packages/coding-agent/src/prompts/system/eager-todo.md +++ b/packages/coding-agent/src/prompts/system/eager-todo.md @@ -1,13 +1,13 @@ Before substantive work, create a phased todo. -You MUST call `todo_write` first in this turn. +You MUST call `todo` first in this turn. You MUST initialize the todo list with a single `init` op. You MUST cover the entire request from investigation through implementation and verification — not just the next immediate step. Task descriptions MUST be specific. A future turn MUST execute them without re-planning. You MUST keep task `content` to a short label (5-10 words). Put file paths, implementation steps, and specifics in `details`. You MUST keep exactly one task `in_progress` and all later tasks `pending`. -After `todo_write` succeeds, continue the request in the same turn. -Do not call `todo_write` again unless task state materially changed. +After `todo` succeeds, continue the request in the same turn. +Do not call `todo` again unless task state materially changed. diff --git a/packages/coding-agent/src/prompts/system/orchestrate-notice.md b/packages/coding-agent/src/prompts/system/orchestrate-notice.md index 84bd5d732..a551baba7 100644 --- a/packages/coding-agent/src/prompts/system/orchestrate-notice.md +++ b/packages/coding-agent/src/prompts/system/orchestrate-notice.md @@ -2,12 +2,12 @@ The user's message above is an **orchestration request**. Execute it as the orchestrator under the contract below. This contract overrides any default tendency to yield early, narrate, or do the work yourself. -You decompose, dispatch, verify, and iterate. Substantial and parallelizable work goes through `task` subagents — that is the whole point of orchestrating. But you are not forbidden from touching the tree: a trivial, self-contained edit is yours to make directly when spawning a subagent for it would cost more than the edit itself. Your tool budget is: reading for planning, `task` for dispatch, `edit`/`write` for trivial inline fixes only, verification (`bun check`, `bun test`, `lsp diagnostics`), git via `bash`, and `todo_write` for tracking. +You decompose, dispatch, verify, and iterate. Substantial and parallelizable work goes through `task` subagents — that is the whole point of orchestrating. But you are not forbidden from touching the tree: a trivial, self-contained edit is yours to make directly when spawning a subagent for it would cost more than the edit itself. Your tool budget is: reading for planning, `task` for dispatch, `edit`/`write` for trivial inline fixes only, verification (`bun check`, `bun test`, `lsp diagnostics`), git via `bash`, and `todo` for tracking. 1. **Do not yield until everything is closed.** A phase finishing is *not* a yield point — launch the next phase in the same turn. Stop only when every requested item is verifiably done, or you hit a concrete [blocked] state that genuinely requires the user. -2. **Enumerate the full surface before dispatching.** If the request references audits, plans, checklists, phase lists, or file lists, expand them into a flat set of items in `todo_write`. "Most of them" or "the important ones" is failure. Re-read the source documents — do not work from memory. +2. **Enumerate the full surface before dispatching.** If the request references audits, plans, checklists, phase lists, or file lists, expand them into a flat set of items in `todo`. "Most of them" or "the important ones" is failure. Re-read the source documents — do not work from memory. 3. **Parallelize maximally; never launch a one-off task.** Every set of edits with disjoint file scope MUST ship as one `task` batch — fan the work as wide as it decomposes. A single-task batch for divisible work is a failure: split it. If you are about to dispatch exactly one subagent, stop — either there is more to run alongside it (find it and batch them) or the change is small enough to make inline yourself (do it). Serialize only when one subagent produces a contract (types, schema, shared module) the next consumes — and state the dependency when you do. 4. **Each `task` assignment is self-contained.** Subagents have no shared context. Spell out: target files (≤3–5 explicit paths, no globs), the change with APIs and patterns, edge cases, and observable acceptance criteria. Do not assume they read the same plan you did. 5. **Verify after every phase before launching the next.** Run the appropriate gate: `bun check` for types, package-scoped `bun test` for behavior, `lsp diagnostics` for changed files. If a phase introduced breakage, dispatch fix-up subagents *before* moving on. Never declare a phase done on a red tree. @@ -20,12 +20,12 @@ You decompose, dispatch, verify, and iterate. Substantial and parallelizable wor 1. **Ingest.** Read every referenced file (audits, plans, prior agent output, current branch state). Run `git status` to see uncommitted changes. -2. **Plan.** Materialize the full work surface in `todo_write` as ordered phases. Within each phase, list the parallelizable units. +2. **Plan.** Materialize the full work surface in `todo` as ordered phases. Within each phase, list the parallelizable units. 3. **Dispatch phase.** Launch all parallel `task` subagents in one call. Wait for the batch. 4. **Verify phase.** Run the gates. On failure, dispatch fix-up subagents and re-verify. Do not advance with a red gate. 5. **Commit phase** (if applicable). Focused message naming the phase. -6. **Advance.** Mark the phase done in `todo_write`, immediately start the next phase. No summary message between phases — keep going. -7. **Final verification.** When the last phase is green, run the full gate set once more and confirm every `todo_write` item is closed. Then yield with a terse status, not a recap. +6. **Advance.** Mark the phase done in `todo`, immediately start the next phase. No summary message between phases — keep going. +7. **Final verification.** When the last phase is green, run the full gate set once more and confirm every `todo` item is closed. Then yield with a terse status, not a recap. diff --git a/packages/coding-agent/src/prompts/system/plan-mode-approved.md b/packages/coding-agent/src/prompts/system/plan-mode-approved.md index 77a4d0a48..970e36a09 100644 --- a/packages/coding-agent/src/prompts/system/plan-mode-approved.md +++ b/packages/coding-agent/src/prompts/system/plan-mode-approved.md @@ -6,10 +6,10 @@ Plan approved. You MUST execute this plan step by step. You have full tool access. You MUST verify each step before proceeding to the next. -{{#has tools "todo_write"}} -Before execution, initialize todo tracking with `todo_write`. -After each completed step, immediately update `todo_write`. -If `todo_write` fails, fix the payload and retry before continuing. +{{#has tools "todo"}} +Before execution, initialize todo tracking with `todo`. +After each completed step, immediately update `todo`. +If `todo` fails, fix the payload and retry before continuing. {{/has}} The plan path is for subagent handoff only. You already have the plan; NEVER read it. diff --git a/packages/coding-agent/src/prompts/system/workflow-notice.md b/packages/coding-agent/src/prompts/system/workflow-notice.md index 624a16446..6830cc6ac 100644 --- a/packages/coding-agent/src/prompts/system/workflow-notice.md +++ b/packages/coding-agent/src/prompts/system/workflow-notice.md @@ -62,7 +62,7 @@ Scale to the ask: "find any bugs" → a few finders, single-vote verify. "thorou -- Decompose the surface first; capture it in `todo_write` when it spans phases. +- Decompose the surface first; capture it in `todo` when it spans phases. - Prefer `schema=` for any agent whose output you branch on. - After a fan-out returns, YOU own correctness: read the artifacts, run the gate, verify before acting. Subagents do the legwork; they don't get the last word. - Keep going until the task is closed — a returned fan-out is a step, not a stopping point. diff --git a/packages/coding-agent/src/prompts/tools/todo-write.md b/packages/coding-agent/src/prompts/tools/todo.md similarity index 100% rename from packages/coding-agent/src/prompts/tools/todo-write.md rename to packages/coding-agent/src/prompts/tools/todo.md diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index cbfe4207c..2691b2167 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -203,7 +203,7 @@ import type { CheckpointState } from "../tools/checkpoint"; import { outputMeta } from "../tools/output-meta"; import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils"; import { isAutoQaEnabled } from "../tools/report-tool-issue"; -import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write"; +import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo"; import { ToolAbortError, ToolError } from "../tools/tool-errors"; import { clampTimeout } from "../tools/tool-timeouts"; import { parseCommandArgs } from "../utils/command-args"; @@ -1807,21 +1807,21 @@ export class AgentSession { if (toolName === "edit" && details?.path) { this.#invalidateFileCacheForPath(details.path); } - if (toolName === "todo_write" && !isError && Array.isArray(details?.phases)) { + if (toolName === "todo" && !isError && Array.isArray(details?.phases)) { this.setTodoPhases(details.phases); } - if (toolName === "todo_write" && isError) { + if (toolName === "todo" && isError) { const errorText = content?.find(part => part.type === "text")?.text; const reminderText = [ "", - "todo_write failed, so todo progress is not visible to the user.", - errorText ? `Failure: ${errorText}` : "Failure: todo_write returned an error.", - "Fix the todo payload and call todo_write again before continuing.", + "todo failed, so todo progress is not visible to the user.", + errorText ? `Failure: ${errorText}` : "Failure: todo returned an error.", + "Fix the todo payload and call todo again before continuing.", "", ].join("\n"); await this.sendCustomMessage( { - customType: "todo-write-error-reminder", + customType: "todo-error-reminder", content: reminderText, display: false, details: { toolName, errorText }, @@ -4994,7 +4994,7 @@ export class AgentSession { // splice mutated canonical `#todoPhases` between tool calls, so the model // observed phase totals shrinking ("5 → 4") after marking tasks done. The // `tasks.todoClearDelay` setting is now inert; completed tasks survive - // until the next explicit `todo_write` call removes them via `rm`/`drop`. + // until the next explicit `todo` call removes them via `rm`/`drop`. /** * Abort current operation and wait for agent to become idle. @@ -6553,16 +6553,16 @@ export class AgentSession { return undefined; } - if (!this.#toolRegistry.has("todo_write")) { - logger.warn("Eager todo enforcement skipped because todo_write is unavailable", { + if (!this.#toolRegistry.has("todo")) { + logger.warn("Eager todo enforcement skipped because todo is unavailable", { activeToolNames: this.agent.state.tools.map(tool => tool.name), }); return undefined; } - const todoWriteToolChoice = buildNamedToolChoice("todo_write", this.model); - if (!todoWriteToolChoice) { - logger.warn("Eager todo enforcement skipped because the current model does not support forcing todo_write", { + const todoToolChoice = buildNamedToolChoice("todo", this.model); + if (!todoToolChoice) { + logger.warn("Eager todo enforcement skipped because the current model does not support forcing todo", { modelApi: this.model?.api, modelId: this.model?.id, }); @@ -6580,7 +6580,7 @@ export class AgentSession { attribution: "agent", timestamp: Date.now(), }, - toolChoice: todoWriteToolChoice, + toolChoice: todoToolChoice, }; } /** diff --git a/packages/coding-agent/src/session/redis-session-storage.ts b/packages/coding-agent/src/session/redis-session-storage.ts index a979b6edd..e4d503d5a 100644 --- a/packages/coding-agent/src/session/redis-session-storage.ts +++ b/packages/coding-agent/src/session/redis-session-storage.ts @@ -1,5 +1,9 @@ import { logger, toError } from "@oh-my-pi/pi-utils"; -import { IndexedSessionStorage, type SessionStorageBackend, type SessionStorageIndexEntry } from "./indexed-session-storage"; +import { + IndexedSessionStorage, + type SessionStorageBackend, + type SessionStorageIndexEntry, +} from "./indexed-session-storage"; /** * Minimal subset of the `bun:redis` `RedisClient` surface used by diff --git a/packages/coding-agent/src/session/session-storage.ts b/packages/coding-agent/src/session/session-storage.ts index c3bb7bcdf..30f8420d8 100644 --- a/packages/coding-agent/src/session/session-storage.ts +++ b/packages/coding-agent/src/session/session-storage.ts @@ -151,7 +151,6 @@ export class FileSessionStorage implements SessionStorage { fs.writeFileSync(fpath, content); } - statSync(path: string): SessionStorageStat { const stats = fs.statSync(path); return { size: stats.size, mtimeMs: stats.mtimeMs, mtime: stats.mtime }; @@ -465,7 +464,6 @@ export class MemorySessionStorage implements SessionStorage { entry.mtimeMs = mtimeMs; } - statSync(path: string): SessionStorageStat { const entry = this.#requireEntry(path); return { diff --git a/packages/coding-agent/src/session/sql-session-storage.ts b/packages/coding-agent/src/session/sql-session-storage.ts index 64b82c589..3778831a6 100644 --- a/packages/coding-agent/src/session/sql-session-storage.ts +++ b/packages/coding-agent/src/session/sql-session-storage.ts @@ -1,4 +1,8 @@ -import { IndexedSessionStorage, type SessionStorageBackend, type SessionStorageIndexEntry } from "./indexed-session-storage"; +import { + IndexedSessionStorage, + type SessionStorageBackend, + type SessionStorageIndexEntry, +} from "./indexed-session-storage"; /** * Supported `bun:sql` adapter dialects. `Bun.SQL` reports this string on @@ -276,7 +280,9 @@ class SqlSessionStorageBackend implements SessionStorageBackend { async readSlices(path: string, prefixBytes: number, suffixBytes: number): Promise<[string, string]> { const values = - this.#adapter === "postgres" ? [prefixBytes, suffixBytes, path] : [prefixBytes, suffixBytes, suffixBytes, path]; + this.#adapter === "postgres" + ? [prefixBytes, suffixBytes, path] + : [prefixBytes, suffixBytes, suffixBytes, path]; const rows = (await this.#client.unsafe(this.#q.readSlices, values)) as SliceRow[]; const row = rows[0]; if (!row) throw enoent(path); diff --git a/packages/coding-agent/src/slash-commands/helpers/todo.ts b/packages/coding-agent/src/slash-commands/helpers/todo.ts index 54158a7db..1fb836e7c 100644 --- a/packages/coding-agent/src/slash-commands/helpers/todo.ts +++ b/packages/coding-agent/src/slash-commands/helpers/todo.ts @@ -1,12 +1,12 @@ import * as path from "node:path"; -import type { TodoPhase } from "../../tools/todo-write"; +import type { TodoPhase } from "../../tools/todo"; import { applyOpsToPhases, getLatestTodoPhasesFromEntries, markdownToPhases, phasesToMarkdown, USER_TODO_EDIT_CUSTOM_TYPE, -} from "../../tools/todo-write"; +} from "../../tools/todo"; import type { ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime } from "../types"; import { commandConsumed, parseSubcommand, usage } from "./parse"; diff --git a/packages/coding-agent/src/task/executor.ts b/packages/coding-agent/src/task/executor.ts index d2a39e215..6096d179e 100644 --- a/packages/coding-agent/src/task/executor.ts +++ b/packages/coding-agent/src/task/executor.ts @@ -1276,7 +1276,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise !parentOwnedToolNames.has(name)); if (filteredSubagentTools.length !== subagentToolNames.length) { await awaitAbortable(session.setActiveToolsByName(filteredSubagentTools)); diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index 8467b7349..d953dfe7b 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -53,7 +53,7 @@ import { reportFindingTool } from "./review"; import { SearchTool } from "./search"; import { SearchToolBm25Tool } from "./search-tool-bm25"; import { loadSshTool } from "./ssh"; -import { type TodoPhase, TodoWriteTool } from "./todo-write"; +import { type TodoPhase, TodoTool } from "./todo"; import { WriteTool } from "./write"; import { YieldTool } from "./yield"; @@ -94,7 +94,7 @@ export * from "./review"; export * from "./search"; export * from "./search-tool-bm25"; export * from "./ssh"; -export * from "./todo-write"; +export * from "./todo"; export * from "./tts"; export * from "./write"; export * from "./yield"; @@ -317,7 +317,7 @@ export const BUILTIN_TOOLS: Record = { task: s => TaskTool.create(s), job: JobTool.createIf, irc: IrcTool.createIf, - todo_write: s => new TodoWriteTool(s), + todo: s => new TodoTool(s), web_search: s => new WebSearchTool(s), search_tool_bm25: SearchToolBm25Tool.createIf, write: s => new WriteTool(s), @@ -419,7 +419,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P if (name === "bash") return true; if (name === "eval") return allowEval; if (name === "debug") return session.settings.get("debug.enabled"); - if (name === "todo_write") return !includeYield && session.settings.get("todo.enabled"); + if (name === "todo") return !includeYield && session.settings.get("todo.enabled"); if (name === "find") return session.settings.get("find.enabled"); if (name === "search") return session.settings.get("search.enabled"); if (name === "github") return session.settings.get("github.enabled"); diff --git a/packages/coding-agent/src/tools/renderers.ts b/packages/coding-agent/src/tools/renderers.ts index be79409f4..eebbe57a1 100644 --- a/packages/coding-agent/src/tools/renderers.ts +++ b/packages/coding-agent/src/tools/renderers.ts @@ -28,7 +28,7 @@ import { resolveToolRenderer } from "./resolve"; import { searchToolRenderer } from "./search"; import { searchToolBm25Renderer } from "./search-tool-bm25"; import { sshToolRenderer } from "./ssh"; -import { todoWriteToolRenderer } from "./todo-write"; +import { todoToolRenderer } from "./todo"; import { writeToolRenderer } from "./write"; type ToolRenderer = { @@ -67,7 +67,7 @@ export const toolRenderers: Record = { search_tool_bm25: searchToolBm25Renderer as ToolRenderer, ssh: sshToolRenderer as ToolRenderer, task: taskToolRenderer as ToolRenderer, - todo_write: todoWriteToolRenderer as ToolRenderer, + todo: todoToolRenderer as ToolRenderer, github: githubToolRenderer as ToolRenderer, goal: goalToolRenderer as ToolRenderer, web_search: webSearchToolRenderer as ToolRenderer, diff --git a/packages/coding-agent/src/tools/todo-write.ts b/packages/coding-agent/src/tools/todo.ts similarity index 93% rename from packages/coding-agent/src/tools/todo-write.ts rename to packages/coding-agent/src/tools/todo.ts index acc831f9f..6d7c67ab0 100644 --- a/packages/coding-agent/src/tools/todo-write.ts +++ b/packages/coding-agent/src/tools/todo.ts @@ -6,7 +6,7 @@ import chalk from "chalk"; import * as z from "zod/v4"; import type { RenderResultOptions } from "../extensibility/custom-tools/types"; import type { Theme } from "../modes/theme/theme"; -import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" }; +import todoDescription from "../prompts/tools/todo.md" with { type: "text" }; import type { ToolSession } from "../sdk"; import type { SessionEntry } from "../session/session-manager"; import { renderStatusLine, renderTreeList } from "../tui"; @@ -40,7 +40,7 @@ export interface TodoCompletionTransition { content: string; } -export interface TodoWriteToolDetails { +export interface TodoToolDetails { phases: TodoPhase[]; storage: "session" | "memory"; completedTasks?: TodoCompletionTransition[]; @@ -68,14 +68,14 @@ const TodoOpEntry = z.object({ text: z.string().optional().describe("note text"), }); -const todoWriteSchema = z +const todoSchema = z .object({ ops: z.array(TodoOpEntry).min(1).describe("ordered todo operations"), }) .describe("apply ordered todo operations"); -type TodoWriteParams = z.infer; -type TodoOpEntryValue = TodoWriteParams["ops"][number]; +type TodoParams = z.infer; +type TodoOpEntryValue = TodoParams["ops"][number]; // ============================================================================= // State helpers @@ -159,7 +159,7 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha } if (entry.type !== "message") continue; const message = entry.message as { role?: string; toolName?: string; details?: unknown; isError?: boolean }; - if (message.role !== "toolResult" || message.toolName !== "todo_write" || message.isError) continue; + if (message.role !== "toolResult" || message.toolName !== "todo" || message.isError) continue; const details = message.details as { phases?: unknown } | undefined; if (!details || !Array.isArray(details.phases)) continue; @@ -178,7 +178,7 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha * the caller can render a `+N more` hint. When every task in `tasks` is * closed (completed or abandoned), returns the trailing `maxVisible` tasks * with `hiddenOpenCount = 0`, so the panel keeps useful context until the - * active-phase pointer advances on the next `todo_write`. + * active-phase pointer advances on the next `todo`. * * Task identity and order are preserved — this is a slice, never a sort. */ @@ -383,7 +383,7 @@ function applyEntry(phases: TodoPhase[], entry: TodoOpEntryValue, errors: string } } -function applyParams(phases: TodoPhase[], params: TodoWriteParams): { phases: TodoPhase[]; errors: string[] } { +function applyParams(phases: TodoPhase[], params: TodoParams): { phases: TodoPhase[]; errors: string[] } { const errors: string[] = []; let next = phases; for (const entry of params.ops) { @@ -393,10 +393,10 @@ function applyParams(phases: TodoPhase[], params: TodoWriteParams): { phases: To return { phases: next, errors }; } -/** Apply an array of `todo_write`-style ops to existing phases. Used by /todo slash command. */ +/** Apply an array of `todo`-style ops to existing phases. Used by /todo slash command. */ export function applyOpsToPhases( currentPhases: TodoPhase[], - ops: TodoWriteParams["ops"], + ops: TodoParams["ops"], ): { phases: TodoPhase[]; errors: string[] } { return applyParams(clonePhases(currentPhases), { ops }); } @@ -586,33 +586,33 @@ function formatSummary(phases: TodoPhase[], errors: string[]): string { // Tool Class // ============================================================================= -export class TodoWriteTool implements AgentTool { - readonly name = "todo_write"; +export class TodoTool implements AgentTool { + readonly name = "todo"; readonly approval = "read" as const; - readonly label = "Todo Write"; + readonly label = "Todo"; readonly summary = "Write a structured todo list to track progress within a session"; readonly description: string; - readonly parameters = todoWriteSchema; + readonly parameters = todoSchema; readonly concurrency = "exclusive"; readonly strict = true; readonly loadMode = "discoverable"; constructor(private readonly session: ToolSession) { - this.description = prompt.render(todoWriteDescription); + this.description = prompt.render(todoDescription); } async execute( _toolCallId: string, - params: TodoWriteParams, + params: TodoParams, _signal?: AbortSignal, - _onUpdate?: AgentToolUpdateCallback, + _onUpdate?: AgentToolUpdateCallback, _context?: AgentToolContext, - ): Promise> { + ): Promise> { const previousPhases = clonePhases(this.session.getTodoPhases?.() ?? []); const { phases: updated, errors } = applyParams(clonePhases(previousPhases), params); const completedTasks = getCompletionTransitions(previousPhases, updated); this.session.setTodoPhases?.(updated); const storage = this.session.getSessionFile() ? "session" : "memory"; - const details: TodoWriteToolDetails = { phases: updated, storage }; + const details: TodoToolDetails = { phases: updated, storage }; if (completedTasks.length > 0) details.completedTasks = completedTasks; return { @@ -627,7 +627,7 @@ export class TodoWriteTool implements AgentTool(); const STRIKE_START = "\x1b[9m"; const STRIKE_END = "\x1b[29m"; @@ -721,11 +721,11 @@ function partialStrikethrough(text: string, visibleChars: number): string { function strikeRevealCount(text: string, frame: number | undefined): number | undefined { if (frame === undefined) return undefined; - if (frame <= TODO_WRITE_STRIKE_HOLD_FRAMES) return 0; + if (frame <= TODO_STRIKE_HOLD_FRAMES) return 0; const chars = [...text]; if (chars.length === 0) return undefined; - const revealFrame = Math.min(frame - TODO_WRITE_STRIKE_HOLD_FRAMES, TODO_WRITE_STRIKE_REVEAL_FRAMES); - return Math.ceil((chars.length * revealFrame) / TODO_WRITE_STRIKE_REVEAL_FRAMES); + const revealFrame = Math.min(frame - TODO_STRIKE_HOLD_FRAMES, TODO_STRIKE_REVEAL_FRAMES); + return Math.ceil((chars.length * revealFrame) / TODO_STRIKE_REVEAL_FRAMES); } function formatTodoLine( @@ -775,8 +775,8 @@ function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme): string[] { return lines; } -export const todoWriteToolRenderer = { - renderCall(args: TodoWriteRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component { +export const todoToolRenderer = { + renderCall(args: TodoRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component { const ops = args?.ops?.map(entry => { const parts = [entry.op ?? "update"]; if (entry.task) parts.push(entry.task); @@ -784,15 +784,15 @@ export const todoWriteToolRenderer = { if (entry.items?.length) parts.push(`${entry.items.length} item${entry.items.length === 1 ? "" : "s"}`); return parts.join(" "); }) ?? ["update"]; - const text = renderStatusLine({ icon: "pending", title: "Todo Write", meta: ops }, uiTheme); + const text = renderStatusLine({ icon: "pending", title: "Todo", meta: ops }, uiTheme); return new Text(text, 0, 0); }, renderResult( - result: { content: Array<{ type: string; text?: string }>; details?: TodoWriteToolDetails }, + result: { content: Array<{ type: string; text?: string }>; details?: TodoToolDetails }, options: RenderResultOptions, uiTheme: Theme, - _args?: TodoWriteRenderArgs, + _args?: TodoRenderArgs, ): Component { const phases = (result.details?.phases ?? []).filter(phase => phase.tasks.length > 0); const completedTasks = result.details?.completedTasks ?? []; @@ -806,10 +806,7 @@ export const todoWriteToolRenderer = { keys.add(task.content); } const allTasks = phases.flatMap(phase => phase.tasks); - const header = renderStatusLine( - { icon: "success", title: "Todo Write", meta: [`${allTasks.length} tasks`] }, - uiTheme, - ); + const header = renderStatusLine({ icon: "success", title: "Todo", meta: [`${allTasks.length} tasks`] }, uiTheme); if (allTasks.length === 0) { const fallback = result.content?.find(content => content.type === "text")?.text ?? "No todos"; return new Text(`${header}\n${uiTheme.fg("dim", fallback)}`, 0, 0); diff --git a/packages/coding-agent/test/acp-agent.test.ts b/packages/coding-agent/test/acp-agent.test.ts index 89e8df91c..8aaeccd99 100644 --- a/packages/coding-agent/test/acp-agent.test.ts +++ b/packages/coding-agent/test/acp-agent.test.ts @@ -944,7 +944,7 @@ describe("ACP agent", () => { await Bun.sleep(0); }); - it("emits ACP plan updates from live todo_write results", async () => { + it("emits ACP plan updates from live todo results", async () => { const harness = await createHarness(); const created = await harness.agent.newSession({ cwd: harness.cwdA, mcpServers: [] }); const session = harness.findSession(created.sessionId)!; @@ -956,7 +956,7 @@ describe("ACP agent", () => { listener({ type: "tool_execution_end", toolCallId: "todo_1", - toolName: "todo_write", + toolName: "todo", isError: false, result: { content: [{ type: "text", text: "updated" }], @@ -976,7 +976,7 @@ describe("ACP agent", () => { listener({ type: "tool_execution_end", toolCallId: "todo_empty", - toolName: "todo_write", + toolName: "todo", isError: false, result: { content: [{ type: "text", text: "cleared" }], @@ -1008,14 +1008,14 @@ describe("ACP agent", () => { await Bun.sleep(0); }); - it("replays todo_write tool results as ACP plan updates", async () => { + it("replays todo tool results as ACP plan updates", async () => { const harness = await createHarness(); const stored = new FakeAgentSession(harness.cwdA); harness.sessions.push(stored); stored.sessionManager.appendMessage({ role: "toolResult", toolCallId: "todo_replay", - toolName: "todo_write", + toolName: "todo", content: [{ type: "text", text: "updated" }], details: { phases: [{ name: "Replay", tasks: [{ content: "Restore plan", status: "pending" }] }], diff --git a/packages/coding-agent/test/agent-session-eager-todo.test.ts b/packages/coding-agent/test/agent-session-eager-todo.test.ts index a040e9faf..bce28b6b6 100644 --- a/packages/coding-agent/test/agent-session-eager-todo.test.ts +++ b/packages/coding-agent/test/agent-session-eager-todo.test.ts @@ -10,7 +10,7 @@ import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { convertToLlm } from "@oh-my-pi/pi-coding-agent/session/messages"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { TodoWriteTool } from "@oh-my-pi/pi-coding-agent/tools"; +import { TodoTool } from "@oh-my-pi/pi-coding-agent/tools"; import { TempDir } from "@oh-my-pi/pi-utils"; import * as z from "zod/v4"; import { createAssistantMessage } from "./helpers/agent-session-setup"; @@ -116,7 +116,7 @@ describe("AgentSession eager todo enforcement", () => { getSessionSpawns: () => "*", settings, }; - const todoWriteTool = new TodoWriteTool(toolSession); + const todoTool = new TodoTool(toolSession); const mockBashTool: AgentTool = { name: "bash", label: "Bash", @@ -130,7 +130,7 @@ describe("AgentSession eager todo enforcement", () => { initialState: { model, systemPrompt: ["Test"], - tools: [todoWriteTool, mockBashTool], + tools: [todoTool, mockBashTool], messages: [], }, convertToLlm, @@ -162,7 +162,7 @@ describe("AgentSession eager todo enforcement", () => { }); const toolRegistry = new Map([ - [todoWriteTool.name, todoWriteTool as unknown as AgentTool], + [todoTool.name, todoTool as unknown as AgentTool], [mockBashTool.name, mockBashTool], ]); @@ -189,8 +189,8 @@ describe("AgentSession eager todo enforcement", () => { expect(observedCalls).toHaveLength(1); expect(observedCalls[0]).toEqual({ - toolChoice: "todo_write", - toolNames: ["todo_write", "bash"], + toolChoice: "todo", + toolNames: ["todo", "bash"], messageRoles: ["user", "user"], messageTexts: [expect.any(String), "list all work trees"], lastMessageRole: "user", @@ -203,7 +203,7 @@ describe("AgentSession eager todo enforcement", () => { it("initializes todos once, then continues within the same user turn", async () => { scriptedResponses = [ - createToolCallAssistantMessage("todo_write", { + createToolCallAssistantMessage("todo", { ops: [ { op: "init", @@ -219,8 +219,8 @@ describe("AgentSession eager todo enforcement", () => { expect(streamCallCount).toBe(2); expect(observedCalls).toHaveLength(2); expect(observedCalls[0]).toEqual({ - toolChoice: "todo_write", - toolNames: ["todo_write", "bash"], + toolChoice: "todo", + toolNames: ["todo", "bash"], messageRoles: ["user", "user"], messageTexts: [expect.any(String), "list all work trees"], lastMessageRole: "user", @@ -239,7 +239,7 @@ describe("AgentSession eager todo enforcement", () => { expect(observedCalls).toHaveLength(1); expect(observedCalls[0]).toEqual({ toolChoice: undefined, - toolNames: ["todo_write", "bash"], + toolNames: ["todo", "bash"], messageRoles: ["user"], messageTexts: ["list all work trees?"], lastMessageRole: "user", @@ -253,7 +253,7 @@ describe("AgentSession eager todo enforcement", () => { expect(observedCalls).toHaveLength(1); expect(observedCalls[0]).toEqual({ toolChoice: undefined, - toolNames: ["todo_write", "bash"], + toolNames: ["todo", "bash"], messageRoles: ["user"], messageTexts: ["list all work trees!"], lastMessageRole: "user", @@ -265,7 +265,7 @@ describe("AgentSession eager todo enforcement", () => { // First prompt: eager todo fires await session.prompt("refactor the parser module"); expect(observedCalls).toHaveLength(1); - expect(observedCalls[0]?.toolChoice).toBe("todo_write"); + expect(observedCalls[0]?.toolChoice).toBe("todo"); // Second prompt: eager todo must NOT fire observedCalls.length = 0; @@ -273,7 +273,7 @@ describe("AgentSession eager todo enforcement", () => { expect(observedCalls).toHaveLength(1); expect(observedCalls[0]).toEqual({ toolChoice: undefined, - toolNames: ["todo_write", "bash"], + toolNames: ["todo", "bash"], messageRoles: expect.arrayContaining(["user"]), messageTexts: expect.arrayContaining(["actually skip that, just fix the typo"]), lastMessageRole: "user", diff --git a/packages/coding-agent/test/agent-session-new-session-todos.test.ts b/packages/coding-agent/test/agent-session-new-session-todos.test.ts index 5340e7bf7..7ff94c906 100644 --- a/packages/coding-agent/test/agent-session-new-session-todos.test.ts +++ b/packages/coding-agent/test/agent-session-new-session-todos.test.ts @@ -10,7 +10,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { TodoWriteTool } from "@oh-my-pi/pi-coding-agent/tools"; +import { TodoTool } from "@oh-my-pi/pi-coding-agent/tools"; import { Snowflake } from "@oh-my-pi/pi-utils"; /** @@ -53,7 +53,7 @@ describe("AgentSession newSession clears todo artifacts", () => { initialState: { model, systemPrompt: ["test"], - tools: [new TodoWriteTool(toolSession)], + tools: [new TodoTool(toolSession)], }, }); diff --git a/packages/coding-agent/test/interactive-mode-todo-clear.test.ts b/packages/coding-agent/test/interactive-mode-todo-clear.test.ts index 624084897..9bf8c3a3c 100644 --- a/packages/coding-agent/test/interactive-mode-todo-clear.test.ts +++ b/packages/coding-agent/test/interactive-mode-todo-clear.test.ts @@ -9,7 +9,7 @@ import { InteractiveMode } from "../src/modes/interactive-mode"; import { AgentSession } from "../src/session/agent-session"; import { AuthStorage } from "../src/session/auth-storage"; import { SessionManager } from "../src/session/session-manager"; -import type { TodoPhase } from "../src/tools/todo-write"; +import type { TodoPhase } from "../src/tools/todo"; function renderTodos(mode: InteractiveMode): string { return Bun.stripANSI(mode.todoContainer.render(120).join("\n")); diff --git a/packages/coding-agent/test/system-prompt-templates.test.ts b/packages/coding-agent/test/system-prompt-templates.test.ts index 62cea3f61..bda89c8b2 100644 --- a/packages/coding-agent/test/system-prompt-templates.test.ts +++ b/packages/coding-agent/test/system-prompt-templates.test.ts @@ -64,7 +64,7 @@ const baseRenderContext: prompt.TemplateContext = { edit: "edit", task: "task", web_search: "web_search", - todo_write: "todo_write", + todo: "todo", inspect_image: "inspect_image", search_tool_bm25: "search_tool_bm25", lsp: "lsp", @@ -73,7 +73,7 @@ const baseRenderContext: prompt.TemplateContext = { grep: "grep", write: "write", }, - tools: ["read", "search", "find", "edit", "task", "web_search", "todo_write"], + tools: ["read", "search", "find", "edit", "task", "web_search", "todo"], worktree: "/tmp/pi-issue-147", writeToolName: "write", }; diff --git a/packages/coding-agent/test/tools/index.test.ts b/packages/coding-agent/test/tools/index.test.ts index 5f8ccad73..bb1da3042 100644 --- a/packages/coding-agent/test/tools/index.test.ts +++ b/packages/coding-agent/test/tools/index.test.ts @@ -79,7 +79,7 @@ describe("createTools", () => { expect(names).toContain("find"); expect(names).toContain("lsp"); expect(names).toContain("task"); - expect(names).toContain("todo_write"); + expect(names).toContain("todo"); expect(names).toContain("web_search"); expect(names).toContain("resolve"); expect(names).not.toContain("fetch"); diff --git a/packages/coding-agent/test/tools/provider-schema-compatibility.test.ts b/packages/coding-agent/test/tools/provider-schema-compatibility.test.ts index f5dd5f11e..0895620ff 100644 --- a/packages/coding-agent/test/tools/provider-schema-compatibility.test.ts +++ b/packages/coding-agent/test/tools/provider-schema-compatibility.test.ts @@ -78,9 +78,9 @@ function formatCompatibilityIssues( } describe("builtin tool schemas provider compatibility", () => { - it("keeps task and todo_write strict-compatible for OpenAI-style providers", async () => { + it("keeps task and todo strict-compatible for OpenAI-style providers", async () => { const toolSchemas = await collectToolSchemas(); - for (const toolName of ["task", "todo_write"]) { + for (const toolName of ["task", "todo"]) { const entry = toolSchemas.find(tool => tool.name === toolName); expect(entry).toBeDefined(); if (!entry) { diff --git a/packages/coding-agent/test/tools/todo-write.test.ts b/packages/coding-agent/test/tools/todo.test.ts similarity index 92% rename from packages/coding-agent/test/tools/todo-write.test.ts rename to packages/coding-agent/test/tools/todo.test.ts index 15059f872..73d0d6a61 100644 --- a/packages/coding-agent/test/tools/todo-write.test.ts +++ b/packages/coding-agent/test/tools/todo.test.ts @@ -4,13 +4,13 @@ import { initTheme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; import { selectStickyTodoWindow, - TODO_WRITE_STRIKE_HOLD_FRAMES, + TODO_STRIKE_HOLD_FRAMES, type TodoItem, type TodoPhase, type TodoStatus, - TodoWriteTool, + TodoTool, todoMatchesAnyDescription, - todoWriteToolRenderer, + todoToolRenderer, } from "@oh-my-pi/pi-coding-agent/tools"; function createSession(initialPhases: TodoPhase[] = []): ToolSession { @@ -32,9 +32,9 @@ beforeAll(async () => { await initTheme(); }); -describe("TodoWriteTool auto-start behavior", () => { +describe("TodoTool auto-start behavior", () => { it("auto-starts the first task after init", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); const result = await tool.execute("call-1", { ops: [ { @@ -47,14 +47,14 @@ describe("TodoWriteTool auto-start behavior", () => { const tasks = result.details?.phases[0]?.tasks ?? []; expect(tasks.map(task => task.status)).toEqual(["in_progress", "pending"]); const summary = result.content.find(part => part.type === "text"); - if (summary?.type !== "text") throw new Error("Expected text summary from todo_write"); + if (summary?.type !== "text") throw new Error("Expected text summary from todo"); expect(summary.text).toContain("Remaining items (2):"); expect(summary.text).toContain("status [in_progress] (Execution)"); expect(summary.text).toContain("diagnostics [pending] (Execution)"); }); it("auto-promotes the next pending task when current task is completed", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { @@ -70,40 +70,40 @@ describe("TodoWriteTool auto-start behavior", () => { expect(tasks.map(task => task.status)).toEqual(["completed", "in_progress"]); expect(result.details?.completedTasks).toEqual([{ phase: "Execution", content: "status" }]); const summary = result.content.find(part => part.type === "text"); - if (summary?.type !== "text") throw new Error("Expected text summary from todo_write"); + if (summary?.type !== "text") throw new Error("Expected text summary from todo"); expect(summary.text).toContain("Remaining items (1):"); expect(summary.text).toContain("diagnostics [in_progress] (Execution)"); const completedResult = await tool.execute("call-3", { ops: [{ op: "done", task: "diagnostics" }] }); const completedSummary = completedResult.content.find(part => part.type === "text"); if (completedSummary?.type !== "text") { - throw new Error("Expected text summary from todo_write"); + throw new Error("Expected text summary from todo"); } expect(completedSummary.text).toContain("Remaining items: none."); }); }); it("renders completed tasks as checked before revealing strikethrough", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [{ op: "init", list: [{ phase: "Execution", items: ["finish"] }] }], }); const result = await tool.execute("call-2", { ops: [{ op: "done", task: "finish" }] }); const options = { expanded: true, isPartial: false, spinnerFrame: 0 }; - const component = todoWriteToolRenderer.renderResult(result, options, theme); + const component = todoToolRenderer.renderResult(result, options, theme); const firstFrame = component.render(120).join("\n"); expect(Bun.stripANSI(firstFrame)).toContain("finish"); expect(firstFrame).not.toContain("\x1b[9m"); - options.spinnerFrame = TODO_WRITE_STRIKE_HOLD_FRAMES + 1; + options.spinnerFrame = TODO_STRIKE_HOLD_FRAMES + 1; const revealFrame = component.render(120).join("\n"); expect(Bun.stripANSI(revealFrame)).toContain("finish"); expect(revealFrame).toContain("\x1b[9m"); }); -describe("TodoWriteTool ops operations", () => { +describe("TodoTool ops operations", () => { it("jumps to a specific task out of order", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { @@ -120,7 +120,7 @@ describe("TodoWriteTool ops operations", () => { }); it("demotes the current in_progress task when starting another", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { @@ -140,7 +140,7 @@ describe("TodoWriteTool ops operations", () => { }); it("appends items to an existing phase", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [{ op: "init", list: [{ phase: "Work", items: ["First"] }] }], }); @@ -163,7 +163,7 @@ describe("TodoWriteTool ops operations", () => { }); it("creates a phase when append targets a missing phase", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [{ op: "init", list: [{ phase: "Work", items: ["First"] }] }], }); @@ -183,7 +183,7 @@ describe("TodoWriteTool ops operations", () => { }); it("marks all tasks in a phase done", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { @@ -202,7 +202,7 @@ describe("TodoWriteTool ops operations", () => { }); it("removes all tasks when rm omits task and phase", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { @@ -220,7 +220,7 @@ describe("TodoWriteTool ops operations", () => { }); it("drops all tasks in a phase", async () => { - const tool = new TodoWriteTool(createSession()); + const tool = new TodoTool(createSession()); await tool.execute("call-1", { ops: [ { diff --git a/packages/tui/src/terminal.ts b/packages/tui/src/terminal.ts index e11604858..931cc3c34 100644 --- a/packages/tui/src/terminal.ts +++ b/packages/tui/src/terminal.ts @@ -439,10 +439,7 @@ export class ProcessTerminal implements Terminal { // settable or permanently-set modes are useful for features we enable. const decrpmMatch = sequence.match(decrpmResponsePattern); if (decrpmMatch) { - this.#resolvePrivateMode( - parseInt(decrpmMatch[1]!, 10), - decrpmMatch[2] !== "0" && decrpmMatch[2] !== "4", - ); + this.#resolvePrivateMode(parseInt(decrpmMatch[1]!, 10), decrpmMatch[2] !== "0" && decrpmMatch[2] !== "4"); return; }