feat(packages/coding-agent): added todo.view docs and non-mutating tests

- Added Unreleased changelog documentation for the new read-only `todo.view` behavior.
- Documented revised bash tool guidance distinguishing safe computation pipelines from byte-trimming commands.
- Documented the cached-model selector fix for dropping the first Enter during refresh.
- Added `TodoTool` tests for `view` on populated and empty lists without mutating session state.
This commit is contained in:
can1357
2026-06-09 20:45:01 +02:00
parent 153c3d6565
commit 6b116cd6cd
2 changed files with 46 additions and 0 deletions
+13
View File
@@ -2,6 +2,19 @@
## [Unreleased]
### Added
- Added a read-only `view` op to the `todo` tool that echoes the current list without mutating state, so the agent can recover exact task text instead of guessing it from memory.
### Changed
- Rewrote the bash tool's coreutils guidance (tool prompt and system prompt) around an explicit litmus: pipelines that compute a new fact (`wc -l`, `sort | uniq -c`, `comm`, `diff`) are legitimate bash, while commands that merely move, page, or trim bytes a dedicated tool can fetch remain banned — output trimming destroys data the `artifact://` capture would have saved.
### Fixed
- Fixed the model selector dropping an immediate Enter when cached models were available but the selector's offline refresh was still pending.
- Fixed the edit tool's post-edit diff preview occasionally echoing a context line twice with out-of-order numbering. Block-boundary context injection classified space-prefixed diff rows as old-file-only, so an unchanged line sitting in a net-offset region (old N / new N+k) was missing from the new file's visibility window; `findBlockContextLines` then re-surfaced it under its post-edit number and the row was spliced in after the adjacent change run. New-file boundary lines are now translated back to pre-edit numbers (the compact-preview renumbering contract) and merged into a single old-numbered insertion pass — also fixing closers below a net-offset edit being dropped or renumbered incorrectly.
## [15.10.9] - 2026-06-09
### Fixed
@@ -234,6 +234,39 @@ describe("TodoTool ops operations", () => {
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks.map(task => task.status)).toEqual(["abandoned", "abandoned"]);
});
it("view echoes state without mutating it", async () => {
const session = createSession([
{
name: "Work",
tasks: [
{ content: "First", status: "pending" },
{ content: "Second", status: "pending" },
],
},
]);
const tool = new TodoTool(session);
const result = await tool.execute("call-1", { ops: [{ op: "view" }] });
const tasks = result.details?.phases[0]?.tasks ?? [];
expect(tasks.map(task => task.status)).toEqual(["pending", "pending"]);
// A read never normalizes or writes session state back.
expect(session.getTodoPhases?.()?.[0]?.tasks.map(task => task.status)).toEqual(["pending", "pending"]);
const summary = result.content.find(part => part.type === "text");
if (summary?.type !== "text") throw new Error("Expected text summary");
expect(summary.text).toContain("First");
expect(summary.text).toContain("Second");
});
it("view on an empty list reports empty, not cleared", async () => {
const tool = new TodoTool(createSession());
const result = await tool.execute("call-1", { ops: [{ op: "view" }] });
const summary = result.content.find(part => part.type === "text");
if (summary?.type !== "text") throw new Error("Expected text summary");
expect(summary.text).toContain("Todo list is empty.");
expect(result.isError).toBeUndefined();
});
});
describe("selectStickyTodoWindow", () => {