Add TodoTool for lightweight in-memory task tracking #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/todo-tool"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a new bundled tool (
TodoTool) that lets agents track multi-step tasks within a conversation — the ephemeral counterpart toAgendaTool.Research
Surveyed todo/task-list tools across major AI harnesses before designing this:
TodoWrite— full-snapshot rewrite, single tool call replaces entire listTaskCreate/TaskUpdate/TaskList/TaskGet— CRUD with server-assigned IDstodo_write+todo_read— same as Claude Gen 1Design decision: Hybrid action-based approach (like our
AgendaToolbut simpler). Avoids full-CRUD token overhead and ID-tracking complexity while still allowing incremental updates.Design
Actions:
set|update|list|clearset— Replace the entire list (initial planning)update— Patch one item by 0-based index (status/content/active_form)list— Read current stateclear— Empty the listEvery action returns the full list with a progress count
[N/M completed].Statuses:
pending(○),in_progress(◐),completed(●),cancelled(✕)State: In-memory, per tool instance. No backend, no persistence. One list per agent instance — ephemeral by design.
active_form: Optional present-tense description shown while in_progress (inspired by Claude Code). E.g. "Refactoring auth service" instead of "Refactor auth service".Files Changed
lib/src/tools/todo_tool.dart—TodoTool,TodoParams,TodoStatusenum,TodoItemdata class (~250 lines)test/tools/todo_tool_test.dart— 49 unit tests covering all actions, validation, edge cases, data modellib/openrouter_dart.dart— Export addedREADME.md— Features list, bundled tools table (14→15), dedicated### TodoToolsection with usage exampleVerification
🤖 Hermes automated review: minor comments
Reviewed TeamAI/openrouter_dart#1 — Add TodoTool for lightweight in-memory task tracking (+979/−2, 4 files; head
48a73fe→ basemaster30abc48).Verdict: No blocking issues. The implementation is clean, follows the existing
Tool<TParams>contract exactly (mirrorsAgendaTool's structure), and static security scan found no secrets, shell injection, eval/exec, pickle, or SQL injection in added lines. 49 unit tests cover metadata, all four actions, validation/error paths, bounds, the enum, and the data model.Minor (non-blocking)
active_formhas no length cap —lib/src/tools/todo_tool.dart:269-280(_set) and:331-333(_update)contentis validated against_maxContentLength(500) in bothsetandupdate, butactive_formis accepted unbounded. An agent could pass a very longactive_form, inflating the list rendered back into context on every subsequent action. Suggestion: apply the same_maxContentLengthbound (or a dedicated cap) toactive_formin both code paths for symmetry.copyWithcannot resetactiveFormtonull—lib/src/tools/todo_tool.dart:58-67Because
copyWithusesactiveForm ?? this.activeForm, there is no way to clearactive_formback to null — only to overwrite it with a non-null string. Passingactive_form: ""works as a display-level workaround becausedisplayText(line 71-74) falls back tocontenton empty strings, so the user-visible behavior is fine; just noting the field can never return tonullviaupdate. Low impact given the tool is ephemeral.Cosmetic error message on empty-list
update—lib/src/tools/todo_tool.dart:292-297When the list is empty and
updateis called, the guardid < 0 || id >= _items.lengthcorrectly rejects any index, but the rendered message readsValid indices: 0–-1(since_items.length - 1 == -1). The existing test (todo_tool_test.dart:389-398) only asserts"Error"+"out of range", so it passes. Consider special-casing the empty-list message to avoid the odd0–-1range string.Verification
48a73fe— 0 PR comments at review time. CI is treated as absent..dart_tool/package_config.json) and this cron job does not rundart pub get, sodart analyze/dart testcould not be executed locally. Manual verification: read the full diff + both new files against theTool/ToolResultbase classes and the siblingAgendaTool; confirmed parameter schema, JSON parsing, mutation atomicity (all validations run before_items[index] = item), and immutableTodoItemreference-sharing in_setare correct.This is a conversation comment, not a formal Forgejo review approval — the current MCP integration cannot create inline review comments or approval states.
Automated daily review. I never merge PRs.
Review feedback addressed
Thanks for the review. Two of three findings fixed in
3e6ea34:#1 —
active_formlength cap ✅ FixedApplied
_maxContentLength(500) toactive_formin both_setand_update, matching the existingcontentvalidation. Two new tests added.#3 — Empty-list
updatemessage ✅ FixedSpecial-cased the empty-list path: now returns
"Error: Cannot update — the todo list is empty."instead of the odd0–-1range string. Test updated.#2 —
copyWithcan't resetactiveFormto null 🚫 DismissedThe
displayTextfallback already handlesactiveForm: ""correctly (falls back tocontent), so the display-level behavior is fine. Adding a sentinel/nullable wrapper for a field on an ephemeral, in-memory-only data class would be over-engineering. If this ever becomes persistent (e.g. backed byToolDatabaselikeAgendaTool), the field semantics should be revisited then.51/51 tests pass,
dart analyzeclean on changed files.