fs write/edit now emit a result-time contextual-diff tool_call_update
(the applied hunk with ±3 context lines, one hunk per replace_all site),
matching what claude-agent-acp sends and what makes an editor render the
change in place. The call-time snippet diff stays; the result hunk
supersedes it (ACP content-replace).
Mechanism:
- A persisted tool-private `meta` channel: execute may return
`{ content, meta }`; `meta` (JsonValue) rides on the tool/result event
and is handed back to presentResult, so the diff reproduces on replay
(event-sourced). JsonValue is now exported from dsh-session.
- The backend returns raw before/after text (storage facts) on
FsWriteOutcome/FsEditOutcome; the tool computes the hunk via the npm
`diff` package's structuredPatch. A create has no before → no result
diff; a failed/aborted mutation carries no meta.
- ToolResultView gains a DiffResultView; the bridge's result-side switch
renders it as {type:'diff'} content blocks.
RFC: docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md
(justifies the npm `diff` runtime dep over vendoring and the meta channel);
the render-intent-union RFC's Non-goal is updated to record this shipped.
All fs snapshot goldens re-recorded; edit/overwrite gain the contextual
result diff, create/read/policy-reject unchanged in structure.
@deepseek-ai/dsh-fs-local
The local-filesystem implementation of the ctx.fs provider seam (@deepseek-ai/dsh-fs). Backs the six FileSystem primitives with the host filesystem; loading it as a plugin populates ctx.fs.
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
await ctx.plugin(LocalFileSystem, { cwd: process.cwd() })
// ctx.fs is now the local backend; load @deepseek-ai/dsh-fs-policy for the
// freshness policy gate and @deepseek-ai/dsh-tool-fs to expose read/write/edit.
Behavior
resolve(path, opts?)— a relativepathresolves againstopts.cwdwhen the caller supplies one (the model-facing tools pass the calling agent's session cwd — see the per-session cwd RFC), elseconfig.cwd(defaultprocess.cwd()); an absolutepathignores both. ThetargetKeyis the file'srealpath, so two input paths reaching the same file through symlinks share one identity, and writes/edits land on the link target (preserving the link). A not-yet-existing path uses the realpathed parent directory plus basename when the parent exists; only an unresolvable parent falls back to the absolute path.displayPathis the absolute (un-resolved) path.stat— returnsFsInfo(version=mtimeMs:size,typeoffile/directory/other, bytesize) orundefinedwhen the target is absent.readText/streamText— UTF-8 only.readTextreads the whole file;streamTextstreams it in chunks (cross-chunk decoding) so a huge file never has to be held whole in memory. Both reject invalid UTF-8 and NUL-byte binary samples (FS_NOT_TEXT) and non-regular targets. Thereadtool (@deepseek-ai/dsh-tool-fs) decides which to call by size and owns the line windowing.writeText— atomic: writes to a temp file opened exclusively (wx,0o600) inside a randomly-named private staging dir (0o700) next to the target, fsyncs, then renames over the target. An existing file's mode is preserved, while new files default to0o600. Theexpectedguard is OPTIONAL: omitting it unconditionally creates-or-overwrites;createIfAbsentcreates a missing target and rejects an existing one (FS_NOT_OBSERVED);replaceIfVersionreplaces only at the observed version (a missing target or mismatch isFS_STALE_VERSION).editText— atomic literal read-modify-write over the same primitive, serialized per target by a mutation lock. Theexpectedguard is OPTIONAL: when supplied it verifies the version BEFORE literal matching (a stale edit reportsFS_STALE_VERSION, neverFS_EDIT_NOT_FOUND/FS_AMBIGUOUS_EDITagainst newer content); omitting it edits the current content unconditionally. A missing target reportsFS_STALE_VERSIONeither way. LF-normalizes for matching, restores the file's dominant CRLF/LF style, and rejects emptyoldString/ zero matches (FS_EDIT_NOT_FOUND) or ambiguous multi-matches withoutreplace_all(FS_AMBIGUOUS_EDIT).
cwd is not a sandbox
config.cwd is a resolution default, not a containment boundary — absolute paths and .. escape it. Enforce containment with a stricter ctx.fs backend or a permission plugin on the tools/execute waterfall. See the filesystem capability-seam RFC's Risks section.
The raw I/O lives in src/fsio.ts (Cordis-free, independently unit-tested); src/index.ts is the thin service wiring.