The fs seam split left four pieces of pre-split surface populated on
every call and read by nobody:
- STREAM_MIN_SIZE + FsIoInternals.streamMinSize in dsh-fs-local: the
backend has no read routing (readWholeText/streamWholeText are
separate primitives the caller picks), and the real 10 MiB routing
constant lives in dsh-tool-fs's read tool. Delete the dead mirror and
the knob whose JSDoc claimed an override that did not exist; the
remaining FsIoInternals knobs stay (the atomic-write tests use them).
- FsTarget.inputPath: a "diagnostics only" field every backend and test
fake had to fabricate, with zero production readers (policy and error
messages use targetKey/displayPath). listDir gave children the bare
entry name, which was nobody's input.
- FsEditOutcome.replacements/.replaceAll: replacements had no reader
(the single-match policy is enforced by the FS_AMBIGUOUS_EDIT /
FS_EDIT_NOT_FOUND throws, whose message keeps the internal count);
replaceAll only echoed the replace_all argument back to
formatEditOutput, which now takes it from the parsed args. The
outcome shrinks to { version, before, after }, parallel to
FsWriteOutcome's backend-discovered fields. Emitted text is unchanged
for both branches (no snapshot churn).
- FileReadOutcome.limit/.version: formatReadOutput renders
offset/lines/totalLines/truncatedByBytes only, and the fs/observed
emit uses info.version directly.
Backends shed four fabrication obligations and gain none. Doc pastes
(core-data-structures/filesystem.md), the dsh-fs README resolve row,
and the test fakes shrink with the types. RFC moved to
implemented/simplification and amended to the shipped shape
(FsEditSpec -> FsEditRequest name fix; manifest rows needed no change).
@deepseek-ai/dsh-fs-local
The local-filesystem implementation of the ctx.fs provider seam (@deepseek-ai/dsh-fs). Backs the seven 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.listDir— lists one directory level in stablename.localeCompare()order. Each entry carries the child basename, type, resolved child target (displayPathunder the listed directory,targetKeyas the realpath identity), and cheap stat metadata (version, plussizefor regular files). It never opens or decodes file contents. Missing targets reportFS_NOT_FOUND, file/special-file targets reportFS_NOT_DIRECTORY, aborted calls reportFS_ABORTED, permission failures reportFS_PERMISSION_DENIED, and other listing or child metadata I/O failures reportFS_IO_ERROR. Broken/disappeared children are returned asotherwithout metadata, but permission/IO failures while resolving a child fail the whole listing with a structuredFsError.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.