Files
deepseek-harness/packages/fs/fs-local/README.md
Tianyi Cui 30c1863755 fix(fs): address review — rename to dsh-fs-policy, fs/*-intent events, RFC currency, ENOTDIR
Rename per review naming decisions:
- package dsh-file-context → dsh-fs-policy (dir, package name, plugin name,
  tsconfig refs, importers, type-equiv manifest, generated catalog + module-graph)
- events fs/write-expectation → fs/write-intent, fs/edit-expectation → fs/edit-intent
  (fs/observed unchanged); type FsWriteExpectation → FsWriteIntent, "expectation"
  wording → "intent" throughout
- exported FileContextExec → FsPolicyExec

Make the implemented RFCs describe what shipped, not the superseded designs:
the 2026-06-17 capability-seam + tool-schemas RFCs no longer place policy on
ctx.fs or use full/partial-view authorization, and the fsspec RFC's ctx.fileContext
service prose is rewritten to the fs/* event-gate reality (freshness-based auth).
Sharpen docs/rfc/implemented/AGENTS.md: a rename is a fact to fix IN PLACE — the
"new RFC" escape hatch is for macro decision reversals only, not renames.

Code fixes from review:
- fsio.ts resolveLocalTarget/probe translate ENOTDIR (a parent path segment is a
  file) into the structured FsError taxonomy instead of leaking a raw Node error;
  resolve reports FS_NOT_FOUND, probe reports absent. Regression tests proven to
  fail on the unfixed code.
- tool-fs HMR test now asserts prompt sections (not just tool schemas) are
  withdrawn on disposal.
- fs/observed is a plain (unguarded) ctx.emit: correct the fs-policy comment,
  filesystem.md, and tool-fs module doc that wrongly claimed the tool "contains"
  a throwing listener; a throw surfaces as the tool's isError result.
- drop the false "loaded by the default product config" claim (no config wires
  the fs tools yet), the duplicate ctx.bash service-map row, the stale
  FileReadRequest catalog link-map entry, and the fs/fs README EOF blank line;
  correct the dsh-fs package.json description.
2026-07-02 03:12:38 +08:00

3.2 KiB

@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) — relative paths resolve from config.cwd (default process.cwd()). The targetKey is the file's realpath, 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. displayPath is the absolute (un-resolved) path.
  • stat — returns FsInfo (version = mtimeMs:size, type of file/directory/other, byte size) or undefined when the target is absent.
  • readText / streamText — UTF-8 only. readText reads the whole file; streamText streams 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. The read tool (@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 to 0o600. The expected guard is OPTIONAL: omitting it unconditionally creates-or-overwrites; createIfAbsent creates a missing target and rejects an existing one (FS_NOT_OBSERVED); replaceIfVersion replaces only at the observed version (a missing target or mismatch is FS_STALE_VERSION).
  • editText — atomic literal read-modify-write over the same primitive, serialized per target by a mutation lock. The expected guard is OPTIONAL: when supplied it verifies the version BEFORE literal matching (a stale edit reports FS_STALE_VERSION, never FS_EDIT_NOT_FOUND/FS_AMBIGUOUS_EDIT against newer content); omitting it edits the current content unconditionally. A missing target reports FS_STALE_VERSION either way. LF-normalizes for matching, restores the file's dominant CRLF/LF style, and rejects empty oldString / zero matches (FS_EDIT_NOT_FOUND) or ambiguous multi-matches without replace_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.