# ctx.fs `FileSystem` (abstract seam) — provided by `@deepseek-ai/dsh-fs`. Abstract filesystem provider service. Subclass, implement the seven storage primitives, and load the subclass as a plugin — it registers as `ctx.fs` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). Semantics every backend must honor: - resolve returns a stable FsTarget; the same underlying file reached by different input paths must yield the same `targetKey` so stale guards and target lookup agree across paths (e.g. through symlinks). - stat returns FsInfo metadata (never content) or `undefined` when the target is absent. - readText/streamText read the whole regular text file (the stream for large files); both own regular-file checks, UTF-8 decoding, binary/NUL rejection, and `FS_NOT_TEXT`. - listDir returns direct children of a directory in stable name order with resolved child targets and cheap metadata only. It never reads file contents. Missing targets throw `FS_NOT_FOUND`, non-directories throw `FS_NOT_DIRECTORY`, permission failures throw `FS_PERMISSION_DENIED`, and other backend I/O failures throw `FS_IO_ERROR`. - writeText is atomic temp-file + rename. `expected` is OPTIONAL: omit it for an unconditional create-or-overwrite (the bare-provider default), or supply a FsWriteIntent to guard the write. - editText verifies `expected.version` BEFORE literal matching (so a stale edit reports `FS_STALE_VERSION`, not `FS_EDIT_NOT_FOUND`/ `FS_AMBIGUOUS_EDIT` against newer content), then applies literal replacement and writes atomically — all inside one mutation critical section. `expected` is OPTIONAL: omit it for an unconditional edit of the current content (a missing target still reports `FS_STALE_VERSION`). [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L172) ### ctx.fs.resolve(path, opts?) ```ts website-api abstract resolve(path: string, opts?: { cwd?: string }): Promise ``` Resolve a model/plugin-supplied path into a stable FsTarget. May perform I/O (a remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence async even though the local backend only normalizes + realpaths. `opts.cwd` is the base directory a RELATIVE `path` resolves against; an absolute `path` ignores it. Omitted ⇒ the backend's own default base (the local backend uses its configured `cwd`). The CALLER supplies this — the seam does not read a session or agent — so a tool can resolve against the caller's per-session workspace (`exec.agent.session.header.cwd`) without the provider depending on `dsh-agent`/`dsh-session`. Mirrors how `dsh-tool-bash` defaults a bash `workdir` to the session cwd. - `path` — the path to resolve; relative paths resolve against `opts.cwd`. - `opts` — `cwd` overrides the backend's default base for relative paths. **Returns** the stable target; the same file yields the same `targetKey`. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L194) ### ctx.fs.stat(target, signal?) ```ts website-api abstract stat(target: FsTarget, signal?: AbortSignal): Promise ``` Return target metadata, or `undefined` when the target does not exist. - `target` — the resolved target to stat. - `signal` — aborts the metadata round-trip. **Returns** metadata only, never content; undefined for an absent target. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L202) ### ctx.fs.readText(target, signal?) ```ts website-api abstract readText(target: FsTarget, signal?: AbortSignal): Promise ``` Read the whole regular text file as a single decoded string. - `target` — the resolved target to read. - `signal` — aborts the read. **Returns** the full decoded UTF-8 content. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L210) ### ctx.fs.streamText(target, signal?) ```ts website-api abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> ``` Stream the whole regular text file as decoded text chunks (same text semantics as readText, for large files). The backend owns cross-chunk UTF-8 decoding and binary rejection so the policy layer never touches raw bytes. - `target` — the resolved target to read. - `signal` — aborts the stream, including between chunks. **Returns** the chunk iterable, decoded and validated like `readText`. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L221) ### ctx.fs.listDir(target, signal?) ```ts website-api abstract listDir(target: FsTarget, signal?: AbortSignal): Promise ``` List direct children of a directory in stable name order. Returns resolved child targets plus cheap metadata only; never reads file contents. - `target` — the resolved directory target. - `signal` — aborts the listing. **Returns** one entry per direct child, in stable name order. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L230) ### ctx.fs.writeText(target, content, expected?, signal?) ```ts website-api abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise ``` Create or fully replace a UTF-8 text file atomically. `expected` is the create-vs-replace decision and stale guard when supplied; OMITTING it is an unconditional create-or-overwrite (the bare provider — no version guard, no read-first requirement). Atomic either way. - `target` — the resolved target to write. - `content` — the full new file content. - `expected` — the write intent guarding the write; omit for unconditional. - `signal` — aborts before the atomic rename takes effect. **Returns** the outcome, including the version the write produced. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L243) ### ctx.fs.editText(target, edit, expected?, signal?) ```ts website-api abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal): Promise ``` Apply a literal edit to an existing UTF-8 text file. When `expected` is supplied, verifies `expected.version` as the stale guard BEFORE literal matching; OMITTING it edits the current content unconditionally (no version guard). Either way applies the replacement and writes atomically — one mutation critical section — and a missing target reports `FS_STALE_VERSION`. - `target` — the resolved target to edit. - `edit` — the literal search/replace request. - `expected` — the version guard; omit for an unconditional edit. - `signal` — aborts before the atomic rename takes effect. **Returns** the outcome, including the version the edit produced. [Source](https://github.com/deepseek-harness/deepseek-harness/blob/master/packages/fs/fs/src/index.ts#L257)