The native adapter's route was named deepseek, colliding with pi-ai's
catalog provider of the same name, so the two DeepSeek paths could never
be mounted side by side. The web settings page needs both configurable at
once. Compositions, fixtures, goldens, scaffolding defaults, and docs all
move together (pre-release, no shim); TUI/session-query-spill/
missing-credential goldens re-recorded through their keyless refresh
modes because provider-name length shifts box padding and spill
truncation points.
The fixture committed the generated Linux workspace path instead of the
`{{cwd}}` token every other acp recording uses. The normalizer masked it
so replay passed, but the next record or refresh would rewrite the file
for no reason; refresh is now byte-stable on it.
The Agent Note's schema-pin arithmetic predated master's
`toolSchemasSource` sharing: seven of the eight sidecars carry the todo
schema, and scenarios composing an identical header share one rather
than each keeping a copy.
The `+N` span sat outside `.summary` to survive its ellipsis, which also
left it outside that rule's font-size and line-height. The web shell
leaves body text at the browser default, so the count rendered larger
than the 14px/24px text beside it on the same 24px row.
Also restores the fs-edit fixture's recorded `/private/tmp` cwd spelling,
which a re-record on Linux had rewritten to `/tmp`. The normalizer already
collapses the `/private` prefix onto the cwd token, so the recorded
spelling replays on both platforms and the fixture matches master.
Boot the glob sampling scenario from a standalone ACP composition that exposes only bash, glob, and grep. Regenerate the smaller header fixtures and trim implementation narration already owned by the Agent Note.
Remove the duplicate model-facing list tool from this branch; directory orientation remains available through bash ls. Keep the glob sampling fix, add a real ACP composition snapshot, and narrow the decision record to the shipped bug fix.
Master had advanced 392 commits. Every conflict was a derived artifact both
sides had regenerated — `docs/tool-catalog.md`, four recorded headless
session logs, and one TUI terminal expectation — so each was resolved by
taking master's version and re-running the generator and the keyless
snapshot refresh on the merged tree. No source file conflicted.
The new `examples/jsonrpc-agent` fixtures keep master's scrubbed
`{{system}}` / `{{tools}}` headers: that suite scrubs request headers, so a
new tool changes nothing there and the refresh output was discarded.
The review made every name a JSON string with `<`, `>`, and `&` escaped. The
hazards behind that are real and each is now covered: a control character
splits one entry across lines, `</` closes the envelope, and a regular file
named `x@` reads as a socket named `x` under the non-regular marker.
Quote those, and only those. `list` is the tool an agent reaches for first
and its output sits in every transcript, so `"archive"/` on every ordinary
line is a permanent cost for a case that almost never occurs. A name is now
emitted verbatim unless it matches a control character, a leading quote, a
backslash, `</`, or a trailing `@`, and is otherwise a JSON string with `</`
neutralized — the delimiter treatment `dsh-workspace-context` already applies
to instruction text, extended to an interpolated path as its
`instruction-frame-paths` TODO asks.
`ctx.fs.listDir` has shipped since the filesystem seam gained it, with skill
discovery as its only consumer; the model-facing tool was deferred to a
separate decision. Nothing else could answer "what is in this directory":
`rg --files` backs glob and grep and never emits a directory entry, so an
empty directory is invisible, no output says which names are directories,
and no output gives an entry count.
`list` takes an optional `path`, defaulting to the session workspace so the
common question needs no argument, and returns the direct children of one
directory with their type. Two presentation rules carry it: directories sort
first, then files, then non-regular children, each alphabetically — so
truncation loses leaves rather than the tree — and the footer always states
the complete listing's size and composition, so a capped view can never read
as a whole directory.
It emits no `fs/observed`: seeing a filename is not reading a file, and a
listing must never satisfy the read-before-write gate.