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:
@@ -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", () => {
|
||||
|
||||
Reference in New Issue
Block a user