From ac28e1a1d3952c47942951fb5c6f99889dced282 Mon Sep 17 00:00:00 2001 From: Dudu-0223 Date: Mon, 22 Jun 2026 14:53:36 +0800 Subject: [PATCH] docs: add filesystem data structures catalog --- docs/cordis-catalog/events-and-services.md | 2 + docs/core-data-structures/core.md | 1 + docs/core-data-structures/filesystem.md | 145 +++++++++++++++++++++ scripts/gen-cordis-catalog.ts | 9 ++ scripts/type-equiv.manifest.json | 17 ++- 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 docs/core-data-structures/filesystem.md diff --git a/docs/cordis-catalog/events-and-services.md b/docs/cordis-catalog/events-and-services.md index a64e962098..0d0cd7b90b 100644 --- a/docs/cordis-catalog/events-and-services.md +++ b/docs/cordis-catalog/events-and-services.md @@ -363,6 +363,8 @@ async write(target: FsTarget, content: string, exec?: FsExecContext, signal?: Ab async edit(target: FsTarget, edit: FsEditRequest, exec?: FsExecContext, signal?: AbortSignal): Promise ``` +Types: [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsExecContext](../core-data-structures/filesystem.md) · [FsExpectation](../core-data-structures/filesystem.md) · [FsReadOutcome](../core-data-structures/filesystem.md) · [FsReadRequest](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) + Source: [`packages/fs/fs/src/index.ts:94`](../../packages/fs/fs/src/index.ts) ### `ctx.llm` — `LlmService` diff --git a/docs/core-data-structures/core.md b/docs/core-data-structures/core.md index b50c3483e4..e6aa7f177f 100644 --- a/docs/core-data-structures/core.md +++ b/docs/core-data-structures/core.md @@ -20,6 +20,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t | [persistence.md](persistence.md) | the durability seam: `SessionPersistence`, JSONL + SQLite backends, `session/flush`, crash recovery, `SessionHeader` | | [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, the `tools/execute` waterfall | | [bash.md](bash.md) | the bash executor seam: `BashExecRequest`/`Spec`, `BashRunResult`, background `BashTask`s | +| [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` | > Type definitions on this page are pasted **verbatim** from source and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Inline JSDoc is omitted for readability; follow the source link for the full contracts. diff --git a/docs/core-data-structures/filesystem.md b/docs/core-data-structures/filesystem.md new file mode 100644 index 0000000000..fc80891ef6 --- /dev/null +++ b/docs/core-data-structures/filesystem.md @@ -0,0 +1,145 @@ +# Filesystem + +The filesystem execution seam is split across three packages: interface ([dsh-fs](../../packages/fs/fs), `ctx.fs`), implementation ([dsh-fs-local](../../packages/fs/fs-local), local disk), and consumer ([dsh-tool-fs](../../packages/fs/tool-fs), the model-facing `read`/`write`/`edit` tools). Filesystem access is an optional capability, not part of the agent-loop spine, so its vocabulary lives here rather than in [core.md](core.md). A sandboxed, remote, virtual, or project-scoped backend can implement the same `FileSystem` service without changing the tool schemas. + +Source: [`packages/fs/fs/src/types.ts`](../../packages/fs/fs/src/types.ts) and [`packages/fs/fs/src/index.ts`](../../packages/fs/fs/src/index.ts) + +## Execution context and target identity + +The filesystem seam needs just enough execution context to derive the observed-file owner. `ToolExecution` satisfies this shape, so `dsh-tool-fs` passes its execution object through without making `dsh-fs` import the tool, agent, or session packages. + +```ts type-equiv +interface FsExecContext { + agent?: { + session?: object + } +} +``` + +Every operation resolves a user-supplied path to an opaque backend target first. Consumers may display `displayPath`, but must not parse `targetKey` or assume it is a local absolute path. + +```ts type-equiv +interface FsTarget { + inputPath: string + targetKey: string + displayPath: string +} +``` + +The backend also owns file-version tokens. `ctx.fs` stores them for stale checks; consumers do not interpret them. + +```ts type-equiv +type FsVersion = string +``` + +## Reads and editable views + +A text read is bounded by line window, byte cap, and backend limits. The returned view records whether the owner saw the whole file or only a partial page; only a `full` view authorizes later write/edit. + +```ts type-equiv +interface FsReadRequest { + offset: number + limit: number +} +``` + +```ts type-equiv +interface FsTextLine { + number: number + text: string +} +``` + +```ts type-equiv +type FsView = 'full' | 'partial' +``` + +```ts type-equiv +interface FsReadOutcome { + offset: number + limit: number + lines: FsTextLine[] + totalLines: number + truncatedByBytes?: true + version: FsVersion + view: FsView +} +``` + +## Write and edit guards + +The base `FileSystem` service converts recorded state into an `FsExpectation` before calling the backend. `observed` carries the stale guard, `partial` means the owner saw a non-editable view, and `unobserved` allows create-if-absent but rejects blind overwrite. + +```ts type-equiv +type FsExpectation = + | { kind: 'observed'; version: FsVersion } + | { kind: 'partial'; version: FsVersion } + | { kind: 'unobserved' } +``` + +```ts type-equiv +interface FsWriteOutcome { + operation: 'create' | 'update' + version: FsVersion +} +``` + +Literal edit is a backend operation, not a `read` plus `write` composed in the tool wrapper. That keeps matching, line-ending handling, stale checks, and atomic replacement inside the filesystem seam. + +```ts type-equiv +interface FsEditRequest { + oldString: string + newString: string + replaceAll: boolean +} +``` + +```ts type-equiv +interface FsEditOutcome { + replacements: number + replaceAll: boolean + version: FsVersion +} +``` + +## Observed-file state + +Observed state is keyed inside the service by owner object and `FsTarget.targetKey`. The owner is normally `exec.agent.session`, but `dsh-fs` treats it as opaque and never reads its fields. A successful read/write/edit refreshes this state for that owner. + +```ts type-equiv +type FsStateSource = 'read' | 'write' | 'edit' +``` + +```ts type-equiv +interface FileState { + targetKey: string + displayPath: string + version: FsVersion + view: FsView + updatedAt: number + source: FsStateSource +} +``` + +## Error taxonomy + +Filesystem failures use stable `FsErrorCode` strings carried by `FsError` (`HarnessError`). The tool registry preserves `{ name, code }` on error results, so retry, permission, and UI layers can branch without parsing text. + +```ts type-equiv +type FsErrorCode = + | 'FS_NOT_FOUND' + | 'FS_NOT_TEXT' + | 'FS_NOT_REGULAR_FILE' + | 'FS_STALE_VERSION' + | 'FS_NOT_OBSERVED' + | 'FS_PARTIAL_OBSERVATION' + | 'FS_AMBIGUOUS_EDIT' + | 'FS_EDIT_NOT_FOUND' + | 'FS_ABORTED' +``` + +`FS_NOT_OBSERVED` means no usable prior observation exists. `FS_PARTIAL_OBSERVATION` means the owner saw only a partial read. `FS_STALE_VERSION` means there was a prior full observation, but the backend version no longer matches. + +## The service + +`FileSystem` (`ctx.fs`, abstract) owns the shared orchestration: `resolve`, `readPage`, `createOrReplace`, and `applyEdit` are backend primitives; public `read`, `write`, and `edit` derive/record owner state and enforce the read-before-write/edit policy before delegating to the backend. The generated wiring catalog shows the exact service signatures on [events-and-services.md](../cordis-catalog/events-and-services.md#ctxfs--filesystem-abstract-seam). diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 7479f5306c..41f8830335 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -75,6 +75,15 @@ const LINK_MAP: Record = { BashRunResult: 'bash.md', BashTask: 'bash.md', BashTaskRead: 'bash.md', + FsEditOutcome: 'filesystem.md', + FsEditRequest: 'filesystem.md', + FsExecContext: 'filesystem.md', + FsExpectation: 'filesystem.md', + FsReadOutcome: 'filesystem.md', + FsReadRequest: 'filesystem.md', + FsTarget: 'filesystem.md', + FsVersion: 'filesystem.md', + FsWriteOutcome: 'filesystem.md', } /** One harness event, extracted from an `interface Events` block. */ diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index f46c4fca6b..d9df0302ab 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -35,6 +35,21 @@ { "doc": "docs/core-data-structures/bash.md", "symbol": "BashRunResult", "source": "packages/bash/bash/src/types.ts" }, { "doc": "docs/core-data-structures/bash.md", "symbol": "CollectedOutput", "source": "packages/bash/bash/src/types.ts" }, { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTask", "source": "packages/bash/bash/src/types.ts" }, - { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTaskRead", "source": "packages/bash/bash/src/types.ts" } + { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTaskRead", "source": "packages/bash/bash/src/types.ts" }, + + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsExecContext", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTarget", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsVersion", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsReadRequest", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTextLine", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsView", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsReadOutcome", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsExpectation", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsWriteOutcome", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsEditRequest", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsEditOutcome", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsStateSource", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FileState", "source": "packages/fs/fs/src/types.ts" }, + { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsErrorCode", "source": "packages/fs/fs/src/types.ts" } ] }