Replace the "bag of optional fields" tool-presentation types
(ToolCallPresentation / ToolResultPresentation / ToolTerminal) with a
card-tagged discriminated union — the standing FIXME(tool-presentation).
A tool declares one render intent per call/result and the ACP bridge
switches on `card`:
ToolCallView = generic | terminal | diff
ToolResultView = generic | terminal
The `diff` card is new: fs write/edit now emit an ACP {type:'diff'}
content block (an editor's inline diff), which the old shapes could not
express. The bridge also relativizes a file card's title against the
session cwd (mirroring claude-agent-acp's toDisplayPath) while keeping
locations/diff paths raw, and derives the no-capability fenced console
fallback from a terminal result's output. read gains the window-in-title
(`Read foo.txt (5 - 8)`) and an always-set location line, matching the
reference adapter field-for-field.
Migrates all three producer families (tool-fs, tool-bash, tool-todo) and
the sole consumer (the ACP bridge) together — the source does not compile
piecewise. Adds snapshot coverage for the terminal _meta path (a new
capability-advertising scenario) and re-records the fs goldens to show the
diff cards. Applied-hunk (result-time, context-line) diffs need a new
result/event shape and are a follow-up.
RFC: docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md
@deepseek-ai/dsh-tool-todo
The model-facing todo_write tool: the agent's whole task list, replaced wholesale on each call.
What it does
Registers one tool, todo_write(todos: [{ content, status }]), on ctx.tools. The model sends the ENTIRE list every call — there are no partial updates or per-item edits. Each call appends a todo/write event (the full list snapshot) to the calling agent's session log via agent.session.append('todo/write', { todos }); the current list is the most recent such event (last-write-wins on replay).
status is one of pending, in_progress, completed — exactly the ACP PlanEntryStatus triple.
Single owner
The list belongs to the ONE agent session that called the tool. There is no subagent/shared/swarm scope: a non-agent caller (no exec.agent) has nowhere to write the list and is rejected. This is a deliberate scope limit — see the RFC.
Validation
Beyond the schema's type/required/enum checks, execute rejects an empty or duplicate content and more than one in_progress task (a coherent plan has at most one task active). Ordering and the discipline of keeping the list current are left to the model via the tool description.
Rendering
The tool writes only the session event; it does not render. UIs subscribe to session/event and render the todo/write data themselves: the stdio UI prints a glyphed checklist, and the ACP bridge maps the list to a plan sessionUpdate (synthesizing the priority ACP requires).
Export shape
A function/namespace plugin: it exports name / inject / apply and NO default. A stray export default would collapse the module via the Loader's unwrapExports and drop inject (see docs/postmortem/0001).