Generator (all four structural gaps):
- harness service pages now render public properties/accessors, not just
methods (ctx.codeRuntime.language/isolation were missing);
- the class page merges the same-named interface half, so ctx.root/baseUrl/
events/logger/reflect/registry appear on Context (vendor root JSDoc gains
prose alongside @experimental);
- Pick<…> heritage on a Context merge resolves to the picked class members,
giving ctx.effect a documented signature on the Fiber page;
- {@link} tags normalize to code spans; merge sections get their own h2 so
reflect members no longer nest under 'Static members'.
verify-website-yaml: reject the unloadable 'group:' pseudo-name (tree.import
only special-cases 'cordis:'; no builtin is registered here) and recurse into
@cordisjs/plugin-group nested entry lists instead.
Prose corrected against loader/cordis source: service.md isolation example
uses the real group plugin + group: true + the required isolate map;
config.md documents concurrent entry startup (Promise.all; order via inject)
and the real hmr defaults (root ['.'], base/ignored/debounce); events.md
fixes emit (synchronous, not parallel), bail (null/false also delegate), and
serial (stops at the first bail value).
7.0 KiB
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
targetKeyso stale guards and target lookup agree across paths (e.g. through symlinks). - stat returns FsInfo metadata (never content) or
undefinedwhen 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 throwFS_NOT_DIRECTORY, permission failures throwFS_PERMISSION_DENIED, and other backend I/O failures throwFS_IO_ERROR. - writeText is atomic temp-file + rename.
expectedis OPTIONAL: omit it for an unconditional create-or-overwrite (the bare-provider default), or supply a FsWriteIntent to guard the write. - editText verifies
expected.versionBEFORE literal matching (so a stale edit reportsFS_STALE_VERSION, notFS_EDIT_NOT_FOUND/FS_AMBIGUOUS_EDITagainst newer content), then applies literal replacement and writes atomically — all inside one mutation critical section.expectedis OPTIONAL: omit it for an unconditional edit of the current content (a missing target still reportsFS_STALE_VERSION).
ctx.fs.resolve(path, opts?)
abstract resolve(path: string, opts?: { cwd?: string }): Promise<FsTarget>
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 againstopts.cwd.opts—cwdoverrides the backend's default base for relative paths.
Returns the stable target; the same file yields the same targetKey.
ctx.fs.stat(target, signal?)
abstract stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>
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.
ctx.fs.readText(target, signal?)
abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
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.
ctx.fs.streamText(target, signal?)
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
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.
ctx.fs.listDir(target, signal?)
abstract listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>
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.
ctx.fs.writeText(target, content, expected?, signal?)
abstract writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal): Promise<FsWriteOutcome>
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.
ctx.fs.editText(target, edit, expected?, signal?)
abstract editText(target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal): Promise<FsEditOutcome>
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.