Merge branch 'feat/subagent-report-semantics' into feat/subagent-settlement-delivery

This commit is contained in:
Hypatia May
2026-08-11 13:02:22 +08:00
252 changed files with 6025 additions and 1149 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md
filesystem.md: 00e28130db9f60e6ad5f8c582ca5531482cdeb32
filesystem.zh.md: 5cf6be12f41b36c8149a941a4d251c4497ed4738
filesystem.md: 01fe2d07f5497374019855ca46ced7e173d21445
filesystem.zh.md: e68246d7a44b8812e132e02979a2c0cda6adb792

View File

@@ -52,7 +52,7 @@ type FsTargetKey = Branded<'FsTargetKey'>
type FsVersion = Branded<'FsVersion'>
```
`stat` returns metadata (never content), or `undefined` when the target is absent. `type` lets the tool reject directories/special files before reading, and `size` lets it choose `readText` vs `streamText` without probing by failure. A protocol consumer that needs a byte ceiling applies it while consuming `streamText`, so the filesystem seam needs no consumer-specific bounded-read primitive.
`stat` returns metadata (never content), or `undefined` when the target is absent. `type` lets consumers reject directories and special files before reading, and `size` lets text consumers choose `readText` vs `streamText` without probing by failure. A text consumer applies its own retention ceiling while consuming `streamText`. Raw-byte consumers use `readBytes(target, signal, maxBytes)`; its required complete-content cap makes a known or discovered overflow fail with `FS_TOO_LARGE` instead of truncating or buffering without a bound.
```ts type-equiv
/**
@@ -256,6 +256,7 @@ type FsErrorCode =
| 'FS_NOT_DIRECTORY'
| 'FS_NOT_TEXT'
| 'FS_NOT_REGULAR_FILE'
| 'FS_TOO_LARGE'
| 'FS_PERMISSION_DENIED'
| 'FS_SANDBOX_DENIED'
| 'FS_IO_ERROR'
@@ -274,7 +275,7 @@ type FsErrorCode =
## The service and the plugin
`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `readBytes`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls from unseen/absent/present state and records `FsObservation` values. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
@@ -373,6 +374,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
*/
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
/**
* Read the whole regular file as raw bytes with no decoding or binary
* rejection. The bound lives at this seam so a backend can never buffer an
* unbounded file: a target known or discovered to exceed `maxBytes` fails
* with `FS_TOO_LARGE` instead of returning a truncated result.
* @param target - the resolved target to read.
* @param signal - aborts the read.
* @param maxBytes - inclusive byte cap on the complete content.
* @returns the full raw content, at most `maxBytes` long.
*/
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
/**
* List direct children of a directory in stable name order. Returns resolved
* child targets plus cheap metadata only; never reads file contents.

View File

@@ -52,7 +52,7 @@ type FsTargetKey = Branded<'FsTargetKey'>
type FsVersion = Branded<'FsVersion'>
```
`stat` 返回元数据(从不返回内容),目标不存在时返回 `undefined`。`type` 让工具在读取前拒绝目录特殊文件;`size` 让工具无需通过失败探测即可选择 `readText` 还是 `streamText`。需要字节上限的协议消费方在消费 `streamText` 时执行该上限,因此文件系统 seam 无需消费方专用的有界读取原语
`stat` 返回元数据(从不返回内容),目标不存在时返回 `undefined`。`type` 让消费方在读取前拒绝目录特殊文件;`size` 让文本消费方无需通过失败探测即可选择 `readText` 还是 `streamText`。文本消费方在消费 `streamText` 时执行自己的保留量上限。原始字节消费方调用 `readBytes(target, signal, maxBytes)`;其必填的完整内容上限会使已知或读取中发现的超限以 `FS_TOO_LARGE` 失败,不会截断结果或无界缓冲
```ts type-equiv
/**
@@ -256,6 +256,7 @@ type FsErrorCode =
| 'FS_NOT_DIRECTORY'
| 'FS_NOT_TEXT'
| 'FS_NOT_REGULAR_FILE'
| 'FS_TOO_LARGE'
| 'FS_PERMISSION_DENIED'
| 'FS_SANDBOX_DENIED'
| 'FS_IO_ERROR'
@@ -274,7 +275,7 @@ type FsErrorCode =
## 服务与插件
`FileSystem``ctx.fs`abstract拥有提供方原语`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:根据未见/缺失/存在状态对写入与编辑意图 waterfall 作出决策,并记录 `FsObservation` 值。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。
`FileSystem``ctx.fs`abstract拥有提供方原语`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`readBytes`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:根据未见/缺失/存在状态对写入与编辑意图 waterfall 作出决策,并记录 `FsObservation` 值。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
@@ -373,6 +374,18 @@ abstract readText(target: FsTarget, signal?: AbortSignal): Promise<string>
*/
abstract streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>
/**
* Read the whole regular file as raw bytes with no decoding or binary
* rejection. The bound lives at this seam so a backend can never buffer an
* unbounded file: a target known or discovered to exceed `maxBytes` fails
* with `FS_TOO_LARGE` instead of returning a truncated result.
* @param target - the resolved target to read.
* @param signal - aborts the read.
* @param maxBytes - inclusive byte cap on the complete content.
* @returns the full raw content, at most `maxBytes` long.
*/
abstract readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>
/**
* List direct children of a directory in stable name order. Returns resolved
* child targets plus cheap metadata only; never reads file contents.

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/tasks.md
tasks.md: 2da8212b3edf7111180b60162108af0aebb249c1
tasks.zh.md: 825a69029ae931f9668cca863c6ebb2324634a9a
tasks.md: 6d205d9a7840aef1c115c97836886f51bed829b9
tasks.zh.md: 59b7c03d240c45e633f55583b3e08776ec470d52

View File

@@ -246,6 +246,30 @@ abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSigna
*/
abstract onTaskDone(listener: TaskDoneListener): () => void
/**
/**
* Register an effect-scoped observer of visible-set changes. It fires after
* every commit that changes what {@link list} returns for that owner —
* registration, every stopping transition (including the one teardown
* performs before it awaits a slow producer), settlement, owner-disposal
* removal, and the emptying that service disposal commits — so an observer
* re-reads rather than accumulating deltas.
*
* Delivery is owner-relative on the same terms as {@link onTaskDone}: an
* observer registered from an unscoped context — a host composition's own
* carrier — sees every owner, while one registered under an agent
* composition's scope sees exactly the agents composed under it.
*
* This is not a superset of {@link onTaskDone}: that one delivers the terminal
* record under first-wins semantics a control surface couples to notice
* delivery, while this one carries no delivery meaning and marks nothing
* reported. Listeners are contained and never awaited.
* @param listener - receives the owner whose visible set changed, or
* `undefined` when an unowned task changed and every caller's set did.
* @returns disposer that unregisters the listener.
*/
abstract onTasksChanged(listener: TasksChangedListener): () => void
/**
* Attach an effect-scoped surface that can read and stop tasks. It serves the
* owners its registering context's scope covers, and {@link start} refuses an
@@ -258,5 +282,5 @@ abstract attachSurface(name: string): () => void
Types: [Agent](core.md)
Source: [`packages/tasks/tasks/src/index.ts:55`](../../packages/tasks/tasks/src/index.ts)
Source: [`packages/tasks/tasks/src/index.ts:58`](../../packages/tasks/tasks/src/index.ts)
<!-- END GENERATED cordis-surface -->

View File

@@ -151,7 +151,7 @@ interface TaskRead {
## 服务行为
抽象的 [`TaskService`](../../packages/tasks/tasks/src/index.ts) Service Definition 规定原子 `start`、限定调用方作用域的 `get` 和 `list`、`read`、`kill`、有界 `wait`、故障隔离的 `onTaskDone` 监听器,以及 `attachSurface` 何时可用;[`LocalTaskService`](../../packages/tasks/tasks-local/src/index.ts) 是其进程局部 Service provider。授权会比较拥有者会话拥有者清理会选择确切的已注册 `Agent` 实例。Service Definition 约定见 [`dsh-tasks`](../../packages/tasks/tasks/README.md),注册表生命周期见 [`dsh-tasks-local`](../../packages/tasks/tasks-local/README.md),面向模型的 Consumer 见 [`dsh-tool-tasks`](../../packages/tasks/tool-tasks/README.md)。
抽象的 [`TaskService`](../../packages/tasks/tasks/src/index.ts) Service Definition 规定原子 `start`、限定调用方作用域的 `get` 和 `list`、`read`、`kill`、有界 `wait`、故障隔离的 `onTaskDone` 与 `onTasksChanged` 监听器,以及 `attachSurface` 何时可用;[`LocalTaskService`](../../packages/tasks/tasks-local/src/index.ts) 是其进程局部 Service provider。授权会比较拥有者会话拥有者清理会选择确切的已注册 `Agent` 实例。Service Definition 约定见 [`dsh-tasks`](../../packages/tasks/tasks/README.md),注册表生命周期见 [`dsh-tasks-local`](../../packages/tasks/tasks-local/README.md),面向模型的 Consumer 见 [`dsh-tool-tasks`](../../packages/tasks/tool-tasks/README.md)。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
@@ -246,6 +246,30 @@ abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSigna
*/
abstract onTaskDone(listener: TaskDoneListener): () => void
/**
/**
* Register an effect-scoped observer of visible-set changes. It fires after
* every commit that changes what {@link list} returns for that owner —
* registration, every stopping transition (including the one teardown
* performs before it awaits a slow producer), settlement, owner-disposal
* removal, and the emptying that service disposal commits — so an observer
* re-reads rather than accumulating deltas.
*
* Delivery is owner-relative on the same terms as {@link onTaskDone}: an
* observer registered from an unscoped context — a host composition's own
* carrier — sees every owner, while one registered under an agent
* composition's scope sees exactly the agents composed under it.
*
* This is not a superset of {@link onTaskDone}: that one delivers the terminal
* record under first-wins semantics a control surface couples to notice
* delivery, while this one carries no delivery meaning and marks nothing
* reported. Listeners are contained and never awaited.
* @param listener - receives the owner whose visible set changed, or
* `undefined` when an unowned task changed and every caller's set did.
* @returns disposer that unregisters the listener.
*/
abstract onTasksChanged(listener: TasksChangedListener): () => void
/**
* Attach an effect-scoped surface that can read and stop tasks. It serves the
* owners its registering context's scope covers, and {@link start} refuses an
@@ -258,5 +282,5 @@ abstract attachSurface(name: string): () => void
Types: [Agent](core.md)
Source: [`packages/tasks/tasks/src/index.ts:55`](../../packages/tasks/tasks/src/index.ts)
Source: [`packages/tasks/tasks/src/index.ts:58`](../../packages/tasks/tasks/src/index.ts)
<!-- END GENERATED cordis-surface -->