146 lines
4.8 KiB
Markdown
146 lines
4.8 KiB
Markdown
# 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).
|