docs: qualify plan-mode timing and workspace no-write claims
ds-review-bot round 3: - set() after a turn's final step lands in a later turn (selection never forces continuation) — the same-turn phrasing overstated the guarantee; set() JSDoc + subsystem page (both languages). - Workspace attach/insert/detach no-op claims now carry the filtered-candidate-prune caveat: mutate() durably prunes stale candidates even when the requested change is a no-op, a state the sessionIds contract explicitly permits; Workspace fence re-synced on both page sides; catalogs regenerated.
This commit is contained in:
@@ -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/client-modules.md
|
||||
client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c
|
||||
client-modules.zh.md: 5da7740e4c9272a3516f1a28712be7c95f29a6b3
|
||||
client-modules.md: 5dc89a87db7e79e4a297002fa059fdeffcceda20
|
||||
client-modules.zh.md: b3aa9c2aecf8e051a41b7b05f05d364b9ff8d2a6
|
||||
|
||||
@@ -27,7 +27,7 @@ interface WebBootEntry {
|
||||
rev: string
|
||||
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
||||
inject?: string[]
|
||||
/** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */
|
||||
/** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
|
||||
immediately?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ interface WebBootEntry {
|
||||
rev: string
|
||||
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
||||
inject?: string[]
|
||||
/** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */
|
||||
/** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
|
||||
immediately?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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/core.md
|
||||
core.md: 1d0bf6c61500817860dfdd452bddc25adbbeffcc
|
||||
core.zh.md: 2b4766dcfcf1901de2bb8a8f9168888a67af7fba
|
||||
core.md: 3a8ccbef4163eabba1cd287284eda68135f2db13
|
||||
core.zh.md: 0cd6a57971ee98c75b52dd45b6f648632dab8a90
|
||||
|
||||
@@ -51,7 +51,6 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
|
||||
| [invariants.md](invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract |
|
||||
| [http-server.md](http-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the static dist fallback, index taps |
|
||||
| [storage.md](storage.md) | the storage subsystem: the backend seam (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` |
|
||||
| [tui.md](tui.md) | the terminal-extension seam: `TuiOverlayRequest`/`Host`/`Session`, close reasons and outcomes, the modal queue |
|
||||
| [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship |
|
||||
| [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap |
|
||||
| [telemetry.md](telemetry.md) | the outbound reporting seam: `TelemetryRecord`/`TelemetrySeverity`, the `TelemetryBackend` contract, the `telemetry/record` redact waterfall |
|
||||
|
||||
@@ -51,7 +51,6 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
|
||||
| [invariants.md](invariants.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件契约 |
|
||||
| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、静态 dist 回退、index 转换 |
|
||||
| [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` |
|
||||
| [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 |
|
||||
| [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 |
|
||||
| [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 |
|
||||
| [telemetry.md](telemetry.md) | 对外上报 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 契约、`telemetry/record` 脱敏 waterfall |
|
||||
|
||||
@@ -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/http-server.md
|
||||
http-server.md: 565df72f2720a1211f50650d4b1bc8d6df4a007a
|
||||
http-server.zh.md: 69e4a0bd7a1576737583625b23fac2ed05d8ca61
|
||||
http-server.md: 200d3eed683f203b65824ea3f62cf56ce3df912f
|
||||
http-server.zh.md: 9a12d3769a93270810927075be5f236875688b91
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](http-server.zh.md)
|
||||
|
||||
[dsh-host-webserver](../../packages/host/webserver) is the web-shape HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.httpServer`, a named-route registry plus index.html transform taps over a static dist fallback. It is not part of the agent-loop spine and not a capability seam — it knows no harness concepts, and every feature surface (the `/api` bridge, plugin bundles, the HMR event stream) is a route some other plugin registers ([layering note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)). Web (browser) shape only: Electron loads dist over `file://` and carries fetch over an IPC bridge, not this server.
|
||||
[dsh-host-webserver](../../packages/host/webserver) is the web-shape HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.httpServer`, a named-route registry, index.html transform taps, and a single claimable fallback seat. It is not part of the agent-loop spine and not a capability seam — it knows no harness concepts, and every feature surface (the `/api` bridge, plugin bundles, the HMR event stream) is a route some other plugin registers ([layering note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)). Web (browser) shape only: Electron loads dist over `file://` and carries fetch over an IPC bridge, not this server.
|
||||
|
||||
Source: [`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts)
|
||||
|
||||
@@ -24,23 +24,21 @@ interface WebRoute {
|
||||
}
|
||||
```
|
||||
|
||||
Match order is fixed: exact table first, then longest matching prefix, then the static dist fallback. Registration order carries no request-facing semantics — named routes are composed to be disjoint, and the fallback answers anything not yet claimed during the boot window. The fallback keeps locked semantics: non-GET/HEAD is 405, traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), and unknown extensions ship as octet-stream ([`static.ts`](../../packages/host/webserver/src/static.ts)).
|
||||
Match order is fixed: exact table first, then longest matching prefix, then the registered fallback. Registration order carries no request-facing semantics — named routes are composed to be disjoint, and the fallback seat answers anything no named route claims; one owner only, a second registration throws. The shipped Web composition claims the seat with [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts), the SPA dist server with locked semantics: non-GET/HEAD is 405, traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), and unknown extensions ship as octet-stream.
|
||||
|
||||
## Config
|
||||
|
||||
```ts type-equiv
|
||||
/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */
|
||||
/** Gateway config: the listen address. */
|
||||
interface Config {
|
||||
/** Listen host; the two supported values are loopback and all-interfaces. */
|
||||
host: '127.0.0.1' | '0.0.0.0'
|
||||
/** Listen port; zero requests an OS-assigned port. */
|
||||
port: number
|
||||
/** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */
|
||||
distIndex: string
|
||||
}
|
||||
```
|
||||
|
||||
`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. `distIndex` is an assembly fact the composing app resolves and injects.
|
||||
`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. The dist location is an assembly fact of the frontend plugin that claims the seat.
|
||||
|
||||
## The service
|
||||
|
||||
|
||||
@@ -24,23 +24,21 @@ interface WebRoute {
|
||||
}
|
||||
```
|
||||
|
||||
匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到静态 dist 回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,启动窗口期内尚未被认领的请求全部由回退应答。回退遵循固定语义:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 提供([`static.ts`](../../packages/host/webserver/src/static.ts))。
|
||||
匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到已注册的回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,任何未被具名路由认领的请求都由回退席位应答;席位只有一个所有者,第二次注册会抛出异常。发布的 Web 组合用 [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts) 认领席位,即遵循固定语义的 SPA dist 服务器:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 发送。
|
||||
|
||||
## 配置
|
||||
|
||||
```ts type-equiv
|
||||
/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */
|
||||
/** Gateway config: the listen address. */
|
||||
interface Config {
|
||||
/** Listen host; the two supported values are loopback and all-interfaces. */
|
||||
host: '127.0.0.1' | '0.0.0.0'
|
||||
/** Listen port; zero requests an OS-assigned port. */
|
||||
port: number
|
||||
/** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */
|
||||
distIndex: string
|
||||
}
|
||||
```
|
||||
|
||||
`host` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。`distIndex` 是组合应用解析后注入的组装事实。
|
||||
`host` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。dist 位置是认领席位的前端插件的组装事实。
|
||||
|
||||
## 服务
|
||||
|
||||
|
||||
@@ -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/permission.md
|
||||
permission.md: 49dd51ff86d0dbce26fca0ed21527f71bb144dec
|
||||
permission.zh.md: c2ab6ecdfb4b80511107bee41e080b06fae45f2c
|
||||
permission.md: ec8cd4a923b617934f1612c31284103266aea4ef
|
||||
permission.zh.md: 4534c33e2715e4a500344f547c7895128877f4fb
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
English | [中文](permission.zh.md)
|
||||
|
||||
The permission-preset layer of [dsh-permission](../../packages/ui/permission) (`ctx.permission`, `PermissionService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. It is one optional capability, not part of the agent-loop spine, and it owns no enforcement: execution, prompt narration, and replay keep reading their knob folds, and a preset switch only records intent and writes through each knob's canonical setter. The [package README](../../packages/ui/permission/README.md) owns composition status and limitations; the [sandbox switching design](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the rationale.
|
||||
The permission-preset layer of [dsh-permission](../../packages/interaction/permission) (`ctx.permission`, `PermissionService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. It is one optional capability, not part of the agent-loop spine, and it owns no enforcement: execution, prompt narration, and replay keep reading their knob folds, and a preset switch only records intent and writes through each knob's canonical setter. The [package README](../../packages/interaction/permission/README.md) owns composition status and limitations; the [sandbox switching design](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the rationale.
|
||||
|
||||
Source: [`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts)
|
||||
Source: [`packages/interaction/permission/src/index.ts`](../../packages/interaction/permission/src/index.ts)
|
||||
|
||||
## The preset table
|
||||
|
||||
@@ -25,7 +25,7 @@ interface PresetSpec {
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** The {@link PermissionService} config: the deployment's preset table. */
|
||||
/** The {@link PermissionService} config: preset table and composition default. */
|
||||
interface Config {
|
||||
/**
|
||||
* The preset table: name → knob bundle. Defaults to `workspace-write`
|
||||
@@ -33,6 +33,11 @@ interface Config {
|
||||
* never). The name `custom` is reserved for the derived not-a-preset state.
|
||||
*/
|
||||
presets?: Record<string, PresetSpec>
|
||||
/**
|
||||
* Default for new sessions. When omitted, the preset matching the composed
|
||||
* sandbox and approval defaults is used.
|
||||
*/
|
||||
defaultPreset?: string
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,13 +56,13 @@ interface PresetOption {
|
||||
value: string
|
||||
/** The display label. */
|
||||
name: string
|
||||
/** One user-facing sentence on what the value means. */
|
||||
/** One user-facing sentence on what the value means; omitted when not configured. */
|
||||
description?: string
|
||||
}
|
||||
```
|
||||
|
||||
## Switching and the `permission/preset` event
|
||||
|
||||
`set(session, name)` resolves the preset (unknown names throw), appends a log-only `permission/preset` event unless `name` is already the effective preset, then writes each knob through its own setter — `setSandboxMode` from [dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) and `setApprovalPolicy` from [dsh-user-approval](../../packages/ui/user-approval) — only when that knob's effective value changes. The selection event precedes the knob events in the same turn, and re-selecting the effective preset appends nothing at all.
|
||||
`set(session, name)` resolves the preset (unknown names throw), appends a log-only `permission/preset` event unless `name` is already the effective preset, then writes each knob through its own setter — `setSandboxMode` from [dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) and `setApprovalPolicy` from [dsh-user-approval](../../packages/interaction/user-approval) — only when that knob's effective value changes. The selection event precedes the knob events in the same turn, and re-selecting the effective preset appends nothing at all.
|
||||
|
||||
`permission/preset` is durable, log-only user intent: it stays out of the model transcript (the knob events own the model-visible consequences through their consumers), and it exists so `current()` can preserve WHICH preset the user chose when two presets share a bundle; `effectivePermissionPreset(events)` folds the last one, and replay needs no catch-up state. The complete event declaration is in the [persistence log event catalog](../persistence-catalog.md); the method signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxpermission--permissionservice).
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[English](permission.md) | 中文
|
||||
|
||||
[dsh-permission](../../packages/ui/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮(knob),即[沙箱模式](sandbox.md)(`sandbox/mode`)与[审批策略](approval.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个权限(Permissions)选择器提供。它是一项可选能力,不属于 agent loop(智能体循环)主干,也不拥有任何强制执行:执行、提示词叙述与回放仍然读取各自旋钮的折叠结果,预设切换只记录意图,并通过每个旋钮各自的规范 setter 写入。[包(package)README](../../packages/ui/permission/README.md) 负责组合状态与限制;[沙箱切换设计](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)负责决策依据。
|
||||
[dsh-permission](../../packages/interaction/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮(knob),即[沙箱模式](sandbox.md)(`sandbox/mode`)与[审批策略](approval.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个权限(Permissions)选择器提供。它是一项可选能力,不属于 agent loop(智能体循环)主干,也不拥有任何强制执行:执行、提示词叙述与回放仍然读取各自旋钮的折叠结果,预设切换只记录意图,并通过每个旋钮各自的规范 setter 写入。[包(package)README](../../packages/interaction/permission/README.md) 负责组合状态与限制;[沙箱切换设计](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)负责决策依据。
|
||||
|
||||
源码:[`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts)
|
||||
源码:[`packages/interaction/permission/src/index.ts`](../../packages/interaction/permission/src/index.ts)
|
||||
|
||||
## 预设表
|
||||
|
||||
@@ -25,7 +25,7 @@ interface PresetSpec {
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** The {@link PermissionService} config: the deployment's preset table. */
|
||||
/** The {@link PermissionService} config: preset table and composition default. */
|
||||
interface Config {
|
||||
/**
|
||||
* The preset table: name → knob bundle. Defaults to `workspace-write`
|
||||
@@ -33,6 +33,11 @@ interface Config {
|
||||
* never). The name `custom` is reserved for the derived not-a-preset state.
|
||||
*/
|
||||
presets?: Record<string, PresetSpec>
|
||||
/**
|
||||
* Default for new sessions. When omitted, the preset matching the composed
|
||||
* sandbox and approval defaults is used.
|
||||
*/
|
||||
defaultPreset?: string
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,13 +56,13 @@ interface PresetOption {
|
||||
value: string
|
||||
/** The display label. */
|
||||
name: string
|
||||
/** One user-facing sentence on what the value means. */
|
||||
/** One user-facing sentence on what the value means; omitted when not configured. */
|
||||
description?: string
|
||||
}
|
||||
```
|
||||
|
||||
## 切换与 `permission/preset` 事件
|
||||
|
||||
`set(session, name)` 解析预设(未知名称抛出异常),在 `name` 尚不是生效预设时追加一条仅记日志的 `permission/preset` 事件,然后通过各旋钮自己的 setter([dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) 的 `setSandboxMode` 与 [dsh-user-approval](../../packages/ui/user-approval) 的 `setApprovalPolicy`)写入,且仅当该旋钮的生效值发生变化时才写。同一轮次内,选择事件先于旋钮事件出现;重新选择当前生效的预设则什么都不追加。
|
||||
`set(session, name)` 解析预设(未知名称抛出异常),在 `name` 尚不是生效预设时追加一条仅记日志的 `permission/preset` 事件,然后通过各旋钮自己的 setter([dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) 的 `setSandboxMode` 与 [dsh-user-approval](../../packages/interaction/user-approval) 的 `setApprovalPolicy`)写入,且仅当该旋钮的生效值发生变化时才写。同一轮次内,选择事件先于旋钮事件出现;重新选择当前生效的预设则什么都不追加。
|
||||
|
||||
`permission/preset` 是持久、仅记日志的用户意图:它不进入模型 transcript(文本记录),模型可见的后果由旋钮事件经各自消费方承担;它存在是为了在两个预设共享同一个旋钮组合时,让 `current()` 仍能保住用户选择的究竟是哪一个预设;`effectivePermissionPreset(events)` 折叠最后一条,回放不需要任何追赶状态。完整事件声明见[持久化日志事件目录](../persistence-catalog.md);方法签名见生成的[服务目录](../cordis-catalog/services.md#ctxpermission--permissionservice)。
|
||||
|
||||
@@ -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/plan.md
|
||||
plan.md: c8bb6c9b6a0bcfd20ed6eadece4552d49f3c757c
|
||||
plan.zh.md: c8acedaef0724581227b57afbf81418a7e538aed
|
||||
plan.md: 661a393b18f9154771c0b9358ad7f016a4fdec17
|
||||
plan.zh.md: 068b63fd9f3bf46b7962031bba154cc7fc7daafd
|
||||
|
||||
@@ -12,7 +12,7 @@ Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/s
|
||||
|
||||
## Pending intent and the step-boundary flush
|
||||
|
||||
Because every session event is turn-enclosed, a user selection is held as pending intent until the next step boundary — within the same turn when one is open. `set(agent, active)` records the pending selection (a no-op when the target equals the logged-or-already-pending state), and `get(agent)` returns `{ active: boolean; pending?: boolean }` — the logged state shaping the current step, plus the optimistic selection awaiting a boundary.
|
||||
Because every session event is turn-enclosed, a user selection is held as pending intent until the next step boundary — the next request derivation, in whichever turn it occurs (selection never forces continuation, so an intent recorded after a turn's final step lands in a later turn). `set(agent, active)` records the pending selection (a no-op when the target equals the logged-or-already-pending state), and `get(agent)` returns `{ active: boolean; pending?: boolean }` — the logged state shaping the current step, plus the optimistic selection awaiting a boundary.
|
||||
|
||||
The sole flush point is a prepended `agent/step` listener — the loop's in-turn interception seam that runs before every request derivation, including turn 1 step 1 and request-recovery retries. Prompt admission itself never flushes: it happens pre-turn, where a `plan/mode` append would land outside any open turn, so a selection made at the prompt is landed by the first step boundary inside the turn it starts. The prepend means the flush runs before the downstream `agent/step` listener chain. A flush failure is contained — plan policy can never block a turn — and the failed append stays pending for a later boundary. A flushed user selection also narrates the switch as one plugin-sourced `user/message` notice, but only when the last logged request header described the other state, so the model is told exactly when its context changed and never redundantly. A pending selection made while idle is process-local and lost on exit before the next boundary ([README limitation](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work)).
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
## 待定意图与步骤边界冲刷
|
||||
|
||||
由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到下一个步骤边界——已有轮次开启时即在同一轮次之内。`set(agent, active)` 记录待定选择(目标值与已记录或已在待定中的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`,即影响当前步骤的已记录状态,加上正在等待边界的乐观选择。
|
||||
由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到下一个步骤边界——即下一次请求派生,落在哪个轮次就在哪个轮次生效(选择绝不强制续行,因此在某轮最后一步之后记录的意图会在之后的轮次落地)。`set(agent, active)` 记录待定选择(目标值与已记录或已在待定中的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`,即影响当前步骤的已记录状态,加上正在等待边界的乐观选择。
|
||||
|
||||
唯一的冲刷点是一个前置(prepend)注册的 `agent/step` 监听器——agent loop 的轮内拦截 seam,在每次请求派生之前运行,包括第 1 轮第 1 步和请求恢复重试。提示词提交本身绝不冲刷:它发生在轮次开启之前,此时追加 `plan/mode` 会落在任何开启的轮次之外,因此在提示词处做出的选择由它开启的轮次内的第一个步骤边界落地。前置注册意味着冲刷先于下游的 `agent/step` 监听器链运行。冲刷失败会被收容(计划策略绝不能阻塞轮次),追加失败的选择保持待定,等待后续边界。已冲刷的用户选择还会以一条插件来源的 `user/message` 通知叙述这次切换,但仅当最后记录的请求头描述的是另一种状态时才叙述,因此模型恰好在上下文变化时被告知,且绝不重复。空闲时做出的待定选择只存在于进程内,进程在下一个边界之前退出即丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。
|
||||
|
||||
|
||||
@@ -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/telemetry.md
|
||||
telemetry.md: 92235a26fe278dfd6ac2b2ea5703b5c0d837b336
|
||||
telemetry.zh.md: e076abd90bd5054ddbfd6fbe7b725ee45d50a038
|
||||
telemetry.md: cf570723650c32f048ee797923a58f09c777455d
|
||||
telemetry.zh.md: 1bcaa86264e3afd741d66d6ee718946e6151496f
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
English | [中文](telemetry.zh.md)
|
||||
|
||||
Outbound session reporting, split as a [capability seam](../capability-seams.md): the seam ([dsh-session-telemetry](../../packages/telemetry/session-telemetry), `ctx.telemetry`) owns the capture points, the fixed chunk projection, the `telemetry/record` redaction waterfall, the handoff cursor, and the minimal backend contract; the backend a deployment loads ([dsh-session-telemetry-otel](../../packages/telemetry/session-telemetry-otel)) is the OpenTelemetry JS SDK's log pipeline configured verbatim. It is one optional capability, not part of the agent-loop spine, and nothing here reaches a model request. The boundary axiom — the harness's aspect ends at `emit()`; batching, retry, queueing, and loss policy belong to the reporting SDK — and the rejected alternatives are pinned in the [revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md); the capture points, cursor, and projection contracts live in the [seam README](../../packages/telemetry/session-telemetry/README.md).
|
||||
Outbound session reporting, split as a [capability seam](../capability-seams.md): the seam ([dsh-session-telemetry](../../packages/session/session-telemetry), `ctx.telemetry`) owns the capture points, the fixed chunk projection, the `telemetry/record` redaction waterfall, the handoff cursor, and the minimal backend contract; the backend a deployment loads ([dsh-session-telemetry-otel](../../packages/session/session-telemetry-otel)) is the OpenTelemetry JS SDK's log pipeline configured verbatim. It is one optional capability, not part of the agent-loop spine, and nothing here reaches a model request. The boundary axiom — the harness's aspect ends at `emit()`; batching, retry, queueing, and loss policy belong to the reporting SDK — and the rejected alternatives are pinned in the [revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md); the capture points, cursor, and projection contracts live in the [seam README](../../packages/session/session-telemetry/README.md).
|
||||
|
||||
Source: [`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts)
|
||||
Source: [`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts)
|
||||
|
||||
## The logical record
|
||||
|
||||
@@ -12,7 +12,7 @@ Source: [`packages/telemetry/session-telemetry/src/index.ts`](../../packages/tel
|
||||
/**
|
||||
* Severity of a telemetry record, pre-mapped at capture so a receiver can
|
||||
* alert with zero configuration: `error` for events whose own outcome flag
|
||||
* says so (`tool/result.isError`, `turn/end` error reasons) and for
|
||||
* says so (the tool-result block's `isError`, `turn/end` error reasons) and for
|
||||
* `agent-error` operational records. Captured events otherwise default to
|
||||
* `info`; `warn` remains available to `telemetry/record` policies and
|
||||
* backends.
|
||||
@@ -69,9 +69,10 @@ interface TelemetryBackend {
|
||||
/**
|
||||
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
||||
* enqueue — the coordinator calls this synchronously from the
|
||||
* `session/event` hot path, so anything slower than a queue push would tax
|
||||
* the agent loop. Errors thrown here are contained by the coordinator and
|
||||
* logged; they never reach the loop.
|
||||
* `session/event` hot path or an explicit canonical-log capture, so anything
|
||||
* slower than a queue push would tax the agent loop or feedback handling.
|
||||
* Errors thrown here are contained by the coordinator and logged; they
|
||||
* never reach the loop.
|
||||
* @param record - the logical record to report; owned by the backend after the call.
|
||||
*/
|
||||
emit(record: TelemetryRecord): void
|
||||
@@ -96,6 +97,8 @@ interface TelemetryBackend {
|
||||
* coordinator emits its dispose-time `shutdown` markers immediately before
|
||||
* calling this). Awaited by the coordinator's dispose; a rejection is
|
||||
* logged as a warning and never fails application teardown.
|
||||
* The coordinator captures dispose-time shutdown markers immediately before
|
||||
* this call for live capture; on-demand capture creates no ops records.
|
||||
* @returns resolves when the backend's pipeline has quiesced.
|
||||
*/
|
||||
shutdown(): Promise<void>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
[English](telemetry.md) | 中文
|
||||
|
||||
对外的会话上报,拆分为一项[能力 seam](../capability-seams.md):seam 一侧([dsh-session-telemetry](../../packages/telemetry/session-telemetry),`ctx.telemetry`)拥有捕获点、固定分片投影、`telemetry/record` 脱敏 waterfall(瀑布式事件)、handoff 游标与最小后端契约;部署方加载的后端([dsh-session-telemetry-otel](../../packages/telemetry/session-telemetry-otel))则是原样配置的 OpenTelemetry JS SDK 日志流水线。它是一项可选能力,不属于 agent loop(智能体循环)主干,这里也没有任何内容会进入模型请求。边界公理(harness 的职责止于 `emit()`;批处理、重试、排队与丢失策略都属于上报 SDK)连同被否决的替代方案,均已在[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)中定案;捕获点、游标与投影的契约见 [seam README](../../packages/telemetry/session-telemetry/README.md)。
|
||||
对外的会话上报,拆分为一项[能力 seam](../capability-seams.md):seam 一侧([dsh-session-telemetry](../../packages/session/session-telemetry),`ctx.telemetry`)拥有捕获点、固定分片投影、`telemetry/record` 脱敏 waterfall(瀑布式事件)、handoff 游标与最小后端契约;部署方加载的后端([dsh-session-telemetry-otel](../../packages/session/session-telemetry-otel))则是原样配置的 OpenTelemetry JS SDK 日志流水线。它是一项可选能力,不属于 agent loop(智能体循环)主干,这里也没有任何内容会进入模型请求。边界公理(harness 的职责止于 `emit()`;批处理、重试、排队与丢失策略都属于上报 SDK)连同被否决的替代方案,均已在[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)中定案;捕获点、游标与投影的契约见 [seam README](../../packages/session/session-telemetry/README.md)。
|
||||
|
||||
源码:[`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts)
|
||||
源码:[`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts)
|
||||
|
||||
## 逻辑记录
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/**
|
||||
* Severity of a telemetry record, pre-mapped at capture so a receiver can
|
||||
* alert with zero configuration: `error` for events whose own outcome flag
|
||||
* says so (`tool/result.isError`, `turn/end` error reasons) and for
|
||||
* says so (the tool-result block's `isError`, `turn/end` error reasons) and for
|
||||
* `agent-error` operational records. Captured events otherwise default to
|
||||
* `info`; `warn` remains available to `telemetry/record` policies and
|
||||
* backends.
|
||||
@@ -69,9 +69,10 @@ interface TelemetryBackend {
|
||||
/**
|
||||
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
||||
* enqueue — the coordinator calls this synchronously from the
|
||||
* `session/event` hot path, so anything slower than a queue push would tax
|
||||
* the agent loop. Errors thrown here are contained by the coordinator and
|
||||
* logged; they never reach the loop.
|
||||
* `session/event` hot path or an explicit canonical-log capture, so anything
|
||||
* slower than a queue push would tax the agent loop or feedback handling.
|
||||
* Errors thrown here are contained by the coordinator and logged; they
|
||||
* never reach the loop.
|
||||
* @param record - the logical record to report; owned by the backend after the call.
|
||||
*/
|
||||
emit(record: TelemetryRecord): void
|
||||
@@ -96,6 +97,8 @@ interface TelemetryBackend {
|
||||
* coordinator emits its dispose-time `shutdown` markers immediately before
|
||||
* calling this). Awaited by the coordinator's dispose; a rejection is
|
||||
* logged as a warning and never fails application teardown.
|
||||
* The coordinator captures dispose-time shutdown markers immediately before
|
||||
* this call for live capture; on-demand capture creates no ops records.
|
||||
* @returns resolves when the backend's pipeline has quiesced.
|
||||
*/
|
||||
shutdown(): Promise<void>
|
||||
|
||||
@@ -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/workspace.md
|
||||
workspace.md: bade3526964055d5bf4448265c5898f1e66a1fce
|
||||
workspace.zh.md: b0437e42b644c1ce11d9a667972f7b236cf4caa1
|
||||
workspace.md: 5614631e828e51f3b515ae4af17451bd9eda1dbb
|
||||
workspace.zh.md: 11e8f0207a8db8b7612d6672cfe744523490aef7
|
||||
|
||||
@@ -68,7 +68,9 @@ interface Workspace {
|
||||
|
||||
/**
|
||||
* Prepend a session to this workspace's candidate account. An already
|
||||
* accounted id resolves without writing. A new id's live or persisted
|
||||
* accounted id resolves without writing, aside from the durable
|
||||
* filtered-candidate prune every accepted mutation performs. A new id's
|
||||
* live or persisted
|
||||
* header cwd must resolve to an existing directory equal to {@link path};
|
||||
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
||||
* writing.
|
||||
@@ -82,7 +84,9 @@ interface Workspace {
|
||||
* with an anchor the session lands before it, without one it appends to the
|
||||
* end. Only the moved id changes position. A session or anchor absent from
|
||||
* the account rejects without writing; a move to the current position
|
||||
* resolves without writing (decided on the domain write chain).
|
||||
* resolves without writing, aside from the durable filtered-candidate
|
||||
* prune every accepted mutation performs; decided on the domain write
|
||||
* chain.
|
||||
* @param sessionId - The accounted session to move.
|
||||
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
||||
* @returns resolution after durability.
|
||||
@@ -91,8 +95,9 @@ interface Workspace {
|
||||
|
||||
/**
|
||||
* Remove a session from this workspace's account. Idempotent: an id not on
|
||||
* the account resolves without writing (decided on the domain write chain,
|
||||
* like attach). Never touches the session's own stored log.
|
||||
* the account resolves without writing, aside from the durable
|
||||
* filtered-candidate prune every accepted mutation performs; decided on
|
||||
* the domain write chain like attach. Never touches the session's own stored log.
|
||||
* @param sessionId - The session to remove.
|
||||
* @returns resolution after durability.
|
||||
*/
|
||||
|
||||
@@ -68,7 +68,9 @@ interface Workspace {
|
||||
|
||||
/**
|
||||
* Prepend a session to this workspace's candidate account. An already
|
||||
* accounted id resolves without writing. A new id's live or persisted
|
||||
* accounted id resolves without writing, aside from the durable
|
||||
* filtered-candidate prune every accepted mutation performs. A new id's
|
||||
* live or persisted
|
||||
* header cwd must resolve to an existing directory equal to {@link path};
|
||||
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
||||
* writing.
|
||||
@@ -82,7 +84,9 @@ interface Workspace {
|
||||
* with an anchor the session lands before it, without one it appends to the
|
||||
* end. Only the moved id changes position. A session or anchor absent from
|
||||
* the account rejects without writing; a move to the current position
|
||||
* resolves without writing (decided on the domain write chain).
|
||||
* resolves without writing, aside from the durable filtered-candidate
|
||||
* prune every accepted mutation performs; decided on the domain write
|
||||
* chain.
|
||||
* @param sessionId - The accounted session to move.
|
||||
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
||||
* @returns resolution after durability.
|
||||
@@ -91,8 +95,9 @@ interface Workspace {
|
||||
|
||||
/**
|
||||
* Remove a session from this workspace's account. Idempotent: an id not on
|
||||
* the account resolves without writing (decided on the domain write chain,
|
||||
* like attach). Never touches the session's own stored log.
|
||||
* the account resolves without writing, aside from the durable
|
||||
* filtered-candidate prune every accepted mutation performs; decided on
|
||||
* the domain write chain like attach. Never touches the session's own stored log.
|
||||
* @param sessionId - The session to remove.
|
||||
* @returns resolution after durability.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user