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:
@@ -20,7 +20,7 @@ Each fact has one home: the tier whose job it is. Elsewhere, link to that home.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Root `AGENTS.md` | Standing orders: rules an agent needs in context in every session, one to three lines each, linking its home | Stories, worked examples, situational procedures, anything restated from a linked home |
|
| Root `AGENTS.md` | Standing orders: rules an agent needs in context in every session, one to three lines each, linking its home | Stories, worked examples, situational procedures, anything restated from a linked home |
|
||||||
| Subtree `AGENTS.md` (`packages/`, `examples/`, `docs/`, `.agents/notes/`) | Orders specific to that subtree | Repo-wide rules the root file already carries |
|
| Subtree `AGENTS.md` (`packages/`, `examples/`, `docs/`, `.agents/notes/`) | Orders specific to that subtree | Repo-wide rules the root file already carries |
|
||||||
| [architecture.md](architecture.md) | The system map: services, the loop, extension seams — read before changing `packages/` | Type shapes (→ core-data-structures), per-package detail (→ package READMEs), decision rationale (→ Agent Notes), implementation-status annotations |
|
| [architecture.md](architecture.md) | The system map: services, the loop, extension seams — read before changing `packages/` | Type shapes (→ subsystems), per-package detail (→ package READMEs), decision rationale (→ Agent Notes), implementation-status annotations |
|
||||||
| [subsystems/](subsystems/core.md) | The type catalog: literal shapes and semantics of the spine and seam vocabulary | Behavior narration (→ architecture.md) |
|
| [subsystems/](subsystems/core.md) | The type catalog: literal shapes and semantics of the spine and seam vocabulary | Behavior narration (→ architecture.md) |
|
||||||
| [Agent Notes](../.agents/notes/README.md) | Decision records under their own lifecycle contract | Migration plans, checklists, and spec-speak once implemented; archived notes are frozen history |
|
| [Agent Notes](../.agents/notes/README.md) | Decision records under their own lifecycle contract | Migration plans, checklists, and spec-speak once implemented; archived notes are frozen history |
|
||||||
| [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — |
|
| [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — |
|
||||||
@@ -37,7 +37,7 @@ Each fact has one home: the tier whose job it is. Elsewhere, link to that home.
|
|||||||
- **Every non-trivial change includes at least one Agent Note in the same PR.** Update the owning note or add one; only mechanical/local edits are exempt ([scope](../.agents/notes/README.md#when-to-write-one)).
|
- **Every non-trivial change includes at least one Agent Note in the same PR.** Update the owning note or add one; only mechanical/local edits are exempt ([scope](../.agents/notes/README.md#when-to-write-one)).
|
||||||
- **One physical line per paragraph** (`verify-md-wrap`): use editor soft-wrap. Code blocks, tables, and list structure keep their formatting; code comments stay under the linter's column limit.
|
- **One physical line per paragraph** (`verify-md-wrap`): use editor soft-wrap. Code blocks, tables, and list structure keep their formatting; code comments stay under the linter's column limit.
|
||||||
- **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses ` ```ts public-api `; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)).
|
- **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type declaration and its original JSDoc use ` ```ts type-equiv `, while a body-stripped public class declaration uses ` ```ts public-api `; register either in the manifest so neither can drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)).
|
||||||
- **The [core-data-structures catalog](subsystems/core.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types ([what counts as core](subsystems/core.md#what-counts-as-core)).
|
- **The [subsystems catalog](subsystems/core.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types ([what counts as core](subsystems/core.md#what-counts-as-core)).
|
||||||
- **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)).
|
- **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)).
|
||||||
- **Comments and JSDoc state complete contracts, not reasoning transcripts.** Preserve behavior, timing, modality, exceptions, consequences, and non-obvious orientation; delete narration, test walkthroughs, review analysis, and code restatement. Keep the local contract and link its rationale. Use [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for details.
|
- **Comments and JSDoc state complete contracts, not reasoning transcripts.** Preserve behavior, timing, modality, exceptions, consequences, and non-obvious orientation; delete narration, test walkthroughs, review analysis, and code restatement. Keep the local contract and link its rationale. Use [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for details.
|
||||||
- Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams".
|
- Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams".
|
||||||
|
|||||||
@@ -824,6 +824,8 @@ Transform one outbound record before it reaches the backend. This waterfall is t
|
|||||||
'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord
|
'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [TelemetryRecord](../subsystems/telemetry.md)
|
||||||
|
|
||||||
Source: [`packages/session/session-telemetry/src/index.ts:43`](../../packages/session/session-telemetry/src/index.ts)
|
Source: [`packages/session/session-telemetry/src/index.ts:43`](../../packages/session/session-telemetry/src/index.ts)
|
||||||
|
|
||||||
## `tools/*`
|
## `tools/*`
|
||||||
@@ -1090,8 +1092,7 @@ The framework events every plugin also sees, beyond the harness vocabulary above
|
|||||||
- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:340`](../../vendor/cordis/src/events.ts))
|
- `internal/listener` — A listener was registered. ([`vendor/cordis/src/events.ts:340`](../../vendor/cordis/src/events.ts))
|
||||||
- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:342`](../../vendor/cordis/src/events.ts))
|
- `internal/dispatch` — An event is being dispatched to listeners. ([`vendor/cordis/src/events.ts:342`](../../vendor/cordis/src/events.ts))
|
||||||
- `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts))
|
- `hmr/change` — A watched source file changed on disk. ([`vendor/hmr/src/index.ts:20`](../../vendor/hmr/src/index.ts))
|
||||||
- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:22`](../../vendor/hmr/src/index.ts))
|
- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/src/index.ts))
|
||||||
- `hmr/config-update-failed` — A watched config-file refresh failed. ([`vendor/hmr/src/index.ts:29`](../../vendor/hmr/src/index.ts))
|
|
||||||
- `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts))
|
- `exit` — The process is exiting on a signal. ([`vendor/loader/src/index.ts:23`](../../vendor/loader/src/index.ts))
|
||||||
- `loader/config-update` — The loader config tree changed. ([`vendor/loader/src/index.ts:24`](../../vendor/loader/src/index.ts))
|
- `loader/config-update` — The loader config tree changed. ([`vendor/loader/src/index.ts:24`](../../vendor/loader/src/index.ts))
|
||||||
- `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts))
|
- `loader/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts))
|
||||||
|
|||||||
@@ -377,6 +377,8 @@ onRebuilt(listener: (id: string, rev: string) => void): () => void
|
|||||||
onGraphChanged(listener: () => void): () => void
|
onGraphChanged(listener: () => void): () => void
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [WebBootGraph](../subsystems/client-modules.md)
|
||||||
|
|
||||||
Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts)
|
Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts)
|
||||||
|
|
||||||
## `ctx.codeRuntime` — `CodeRuntime` (abstract seam)
|
## `ctx.codeRuntime` — `CodeRuntime` (abstract seam)
|
||||||
@@ -866,6 +868,8 @@ tapIndex(transform: (html: string) => string): () => void
|
|||||||
applyIndexTaps(html: string): string
|
applyIndexTaps(html: string): string
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [WebRoute](../subsystems/http-server.md)
|
||||||
|
|
||||||
Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts)
|
Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts)
|
||||||
|
|
||||||
## `ctx.invariants` — `InvariantService`
|
## `ctx.invariants` — `InvariantService`
|
||||||
@@ -884,6 +888,8 @@ Package-owned invariant registry with global and regex-based selection.
|
|||||||
register(packageName: string, installer: InvariantInstaller): () => void
|
register(packageName: string, installer: InvariantInstaller): () => void
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [InvariantInstaller](../subsystems/invariants.md)
|
||||||
|
|
||||||
Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts)
|
Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts)
|
||||||
|
|
||||||
## `ctx.llm` — `LlmService`
|
## `ctx.llm` — `LlmService`
|
||||||
@@ -1060,7 +1066,7 @@ optionOf(name: string): PresetOption
|
|||||||
set(session: Session, name: string): void
|
set(session: Session, name: string): void
|
||||||
```
|
```
|
||||||
|
|
||||||
Types: [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md)
|
Types: [PresetOption](../subsystems/permission.md) · [PresetSpec](../subsystems/permission.md) · [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md)
|
||||||
|
|
||||||
Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts)
|
Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts)
|
||||||
|
|
||||||
@@ -2036,6 +2042,8 @@ mount<K extends keyof StorageForms>(form: K, facility: StorageForms[K]): () => v
|
|||||||
form<K extends keyof StorageForms>(form: K): StorageForms[K]
|
form<K extends keyof StorageForms>(form: K): StorageForms[K]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [StorageForms](../subsystems/storage.md)
|
||||||
|
|
||||||
Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts)
|
Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts)
|
||||||
|
|
||||||
## `ctx.storageDomain` — `DomainFacility`
|
## `ctx.storageDomain` — `DomainFacility`
|
||||||
@@ -2079,6 +2087,8 @@ get(name: string): DomainImpl | undefined
|
|||||||
async closeAll(): Promise<void>
|
async closeAll(): Promise<void>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [Domain](../subsystems/storage.md) · [DomainSpec](../subsystems/storage.md)
|
||||||
|
|
||||||
Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts)
|
Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts)
|
||||||
|
|
||||||
## `ctx.subagents` — `SubagentService`
|
## `ctx.subagents` — `SubagentService`
|
||||||
@@ -2473,6 +2483,8 @@ flush?(): void
|
|||||||
abstract shutdown(): Promise<void>
|
abstract shutdown(): Promise<void>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Types: [TelemetryRecord](../subsystems/telemetry.md)
|
||||||
|
|
||||||
Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts)
|
Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts)
|
||||||
|
|
||||||
## `ctx.tokenMeter` — `TokenMeterService`
|
## `ctx.tokenMeter` — `TokenMeterService`
|
||||||
@@ -2885,7 +2897,7 @@ archiveSession(sessionId: SessionId): Promise<void>
|
|||||||
async resolveByPath(path: string): Promise<Workspace | undefined>
|
async resolveByPath(path: string): Promise<Workspace | undefined>
|
||||||
```
|
```
|
||||||
|
|
||||||
Types: [SessionId](../subsystems/core.md)
|
Types: [SessionId](../subsystems/core.md) · [Workspace](../subsystems/workspace.md) · [WorkspaceId](../subsystems/workspace.md)
|
||||||
|
|
||||||
Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts)
|
Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts)
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
|
||||||
client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c
|
client-modules.md: 5dc89a87db7e79e4a297002fa059fdeffcceda20
|
||||||
client-modules.zh.md: 5da7740e4c9272a3516f1a28712be7c95f29a6b3
|
client-modules.zh.md: b3aa9c2aecf8e051a41b7b05f05d364b9ff8d2a6
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface WebBootEntry {
|
|||||||
rev: string
|
rev: string
|
||||||
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
||||||
inject?: string[]
|
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
|
immediately?: boolean
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface WebBootEntry {
|
|||||||
rev: string
|
rev: string
|
||||||
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
/** Package-name dependency edges, informational (preflight display / HMR diffing). */
|
||||||
inject?: string[]
|
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
|
immediately?: boolean
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/core.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/core.md
|
||||||
core.md: 1d0bf6c61500817860dfdd452bddc25adbbeffcc
|
core.md: 3a8ccbef4163eabba1cd287284eda68135f2db13
|
||||||
core.zh.md: 2b4766dcfcf1901de2bb8a8f9168888a67af7fba
|
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 |
|
| [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 |
|
| [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` |
|
| [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 |
|
| [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 |
|
| [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 |
|
| [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`、空配套插件契约 |
|
| [invariants.md](invariants.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件契约 |
|
||||||
| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、静态 dist 回退、index 转换 |
|
| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、静态 dist 回退、index 转换 |
|
||||||
| [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` |
|
| [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` 的关系 |
|
| [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 |
|
||||||
| [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 |
|
| [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 |
|
||||||
| [telemetry.md](telemetry.md) | 对外上报 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 契约、`telemetry/record` 脱敏 waterfall |
|
| [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;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/http-server.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/http-server.md
|
||||||
http-server.md: 565df72f2720a1211f50650d4b1bc8d6df4a007a
|
http-server.md: 200d3eed683f203b65824ea3f62cf56ce3df912f
|
||||||
http-server.zh.md: 69e4a0bd7a1576737583625b23fac2ed05d8ca61
|
http-server.zh.md: 9a12d3769a93270810927075be5f236875688b91
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
English | [中文](http-server.zh.md)
|
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)
|
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
|
## Config
|
||||||
|
|
||||||
```ts type-equiv
|
```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 {
|
interface Config {
|
||||||
/** Listen host; the two supported values are loopback and all-interfaces. */
|
/** Listen host; the two supported values are loopback and all-interfaces. */
|
||||||
host: '127.0.0.1' | '0.0.0.0'
|
host: '127.0.0.1' | '0.0.0.0'
|
||||||
/** Listen port; zero requests an OS-assigned port. */
|
/** Listen port; zero requests an OS-assigned port. */
|
||||||
port: number
|
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
|
## 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
|
```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 {
|
interface Config {
|
||||||
/** Listen host; the two supported values are loopback and all-interfaces. */
|
/** Listen host; the two supported values are loopback and all-interfaces. */
|
||||||
host: '127.0.0.1' | '0.0.0.0'
|
host: '127.0.0.1' | '0.0.0.0'
|
||||||
/** Listen port; zero requests an OS-assigned port. */
|
/** Listen port; zero requests an OS-assigned port. */
|
||||||
port: number
|
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;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/permission.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/permission.md
|
||||||
permission.md: 49dd51ff86d0dbce26fca0ed21527f71bb144dec
|
permission.md: ec8cd4a923b617934f1612c31284103266aea4ef
|
||||||
permission.zh.md: c2ab6ecdfb4b80511107bee41e080b06fae45f2c
|
permission.zh.md: 4534c33e2715e4a500344f547c7895128877f4fb
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
English | [中文](permission.zh.md)
|
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
|
## The preset table
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ interface PresetSpec {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts type-equiv
|
```ts type-equiv
|
||||||
/** The {@link PermissionService} config: the deployment's preset table. */
|
/** The {@link PermissionService} config: preset table and composition default. */
|
||||||
interface Config {
|
interface Config {
|
||||||
/**
|
/**
|
||||||
* The preset table: name → knob bundle. Defaults to `workspace-write`
|
* 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.
|
* never). The name `custom` is reserved for the derived not-a-preset state.
|
||||||
*/
|
*/
|
||||||
presets?: Record<string, PresetSpec>
|
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
|
value: string
|
||||||
/** The display label. */
|
/** The display label. */
|
||||||
name: string
|
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
|
description?: string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Switching and the `permission/preset` event
|
## 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).
|
`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) | 中文
|
[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
|
```ts type-equiv
|
||||||
/** The {@link PermissionService} config: the deployment's preset table. */
|
/** The {@link PermissionService} config: preset table and composition default. */
|
||||||
interface Config {
|
interface Config {
|
||||||
/**
|
/**
|
||||||
* The preset table: name → knob bundle. Defaults to `workspace-write`
|
* 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.
|
* never). The name `custom` is reserved for the derived not-a-preset state.
|
||||||
*/
|
*/
|
||||||
presets?: Record<string, PresetSpec>
|
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
|
value: string
|
||||||
/** The display label. */
|
/** The display label. */
|
||||||
name: string
|
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
|
description?: string
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 切换与 `permission/preset` 事件
|
## 切换与 `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)。
|
`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;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/plan.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/plan.md
|
||||||
plan.md: c8bb6c9b6a0bcfd20ed6eadece4552d49f3c757c
|
plan.md: 661a393b18f9154771c0b9358ad7f016a4fdec17
|
||||||
plan.zh.md: c8acedaef0724581227b57afbf81418a7e538aed
|
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
|
## 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)).
|
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))。
|
唯一的冲刷点是一个前置(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;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md
|
||||||
telemetry.md: 92235a26fe278dfd6ac2b2ea5703b5c0d837b336
|
telemetry.md: cf570723650c32f048ee797923a58f09c777455d
|
||||||
telemetry.zh.md: e076abd90bd5054ddbfd6fbe7b725ee45d50a038
|
telemetry.zh.md: 1bcaa86264e3afd741d66d6ee718946e6151496f
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
English | [中文](telemetry.zh.md)
|
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
|
## 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
|
* Severity of a telemetry record, pre-mapped at capture so a receiver can
|
||||||
* alert with zero configuration: `error` for events whose own outcome flag
|
* 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
|
* `agent-error` operational records. Captured events otherwise default to
|
||||||
* `info`; `warn` remains available to `telemetry/record` policies and
|
* `info`; `warn` remains available to `telemetry/record` policies and
|
||||||
* backends.
|
* backends.
|
||||||
@@ -69,9 +69,10 @@ interface TelemetryBackend {
|
|||||||
/**
|
/**
|
||||||
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
||||||
* enqueue — the coordinator calls this synchronously from the
|
* enqueue — the coordinator calls this synchronously from the
|
||||||
* `session/event` hot path, so anything slower than a queue push would tax
|
* `session/event` hot path or an explicit canonical-log capture, so anything
|
||||||
* the agent loop. Errors thrown here are contained by the coordinator and
|
* slower than a queue push would tax the agent loop or feedback handling.
|
||||||
* logged; they never reach the loop.
|
* 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.
|
* @param record - the logical record to report; owned by the backend after the call.
|
||||||
*/
|
*/
|
||||||
emit(record: TelemetryRecord): void
|
emit(record: TelemetryRecord): void
|
||||||
@@ -96,6 +97,8 @@ interface TelemetryBackend {
|
|||||||
* coordinator emits its dispose-time `shutdown` markers immediately before
|
* coordinator emits its dispose-time `shutdown` markers immediately before
|
||||||
* calling this). Awaited by the coordinator's dispose; a rejection is
|
* calling this). Awaited by the coordinator's dispose; a rejection is
|
||||||
* logged as a warning and never fails application teardown.
|
* 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.
|
* @returns resolves when the backend's pipeline has quiesced.
|
||||||
*/
|
*/
|
||||||
shutdown(): Promise<void>
|
shutdown(): Promise<void>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
[English](telemetry.md) | 中文
|
[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
|
* Severity of a telemetry record, pre-mapped at capture so a receiver can
|
||||||
* alert with zero configuration: `error` for events whose own outcome flag
|
* 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
|
* `agent-error` operational records. Captured events otherwise default to
|
||||||
* `info`; `warn` remains available to `telemetry/record` policies and
|
* `info`; `warn` remains available to `telemetry/record` policies and
|
||||||
* backends.
|
* backends.
|
||||||
@@ -69,9 +69,10 @@ interface TelemetryBackend {
|
|||||||
/**
|
/**
|
||||||
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
* Hand one record to the backend's pipeline. MUST be a non-blocking
|
||||||
* enqueue — the coordinator calls this synchronously from the
|
* enqueue — the coordinator calls this synchronously from the
|
||||||
* `session/event` hot path, so anything slower than a queue push would tax
|
* `session/event` hot path or an explicit canonical-log capture, so anything
|
||||||
* the agent loop. Errors thrown here are contained by the coordinator and
|
* slower than a queue push would tax the agent loop or feedback handling.
|
||||||
* logged; they never reach the loop.
|
* 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.
|
* @param record - the logical record to report; owned by the backend after the call.
|
||||||
*/
|
*/
|
||||||
emit(record: TelemetryRecord): void
|
emit(record: TelemetryRecord): void
|
||||||
@@ -96,6 +97,8 @@ interface TelemetryBackend {
|
|||||||
* coordinator emits its dispose-time `shutdown` markers immediately before
|
* coordinator emits its dispose-time `shutdown` markers immediately before
|
||||||
* calling this). Awaited by the coordinator's dispose; a rejection is
|
* calling this). Awaited by the coordinator's dispose; a rejection is
|
||||||
* logged as a warning and never fails application teardown.
|
* 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.
|
* @returns resolves when the backend's pipeline has quiesced.
|
||||||
*/
|
*/
|
||||||
shutdown(): Promise<void>
|
shutdown(): Promise<void>
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# 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:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write docs/subsystems/workspace.md
|
# pnpm run verify-translation-pairing --write docs/subsystems/workspace.md
|
||||||
workspace.md: bade3526964055d5bf4448265c5898f1e66a1fce
|
workspace.md: 5614631e828e51f3b515ae4af17451bd9eda1dbb
|
||||||
workspace.zh.md: b0437e42b644c1ce11d9a667972f7b236cf4caa1
|
workspace.zh.md: 11e8f0207a8db8b7612d6672cfe744523490aef7
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend a session to this workspace's candidate account. An already
|
* 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};
|
* header cwd must resolve to an existing directory equal to {@link path};
|
||||||
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
||||||
* writing.
|
* writing.
|
||||||
@@ -82,7 +84,9 @@ interface Workspace {
|
|||||||
* with an anchor the session lands before it, without one it appends to the
|
* 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
|
* end. Only the moved id changes position. A session or anchor absent from
|
||||||
* the account rejects without writing; a move to the current position
|
* 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 sessionId - The accounted session to move.
|
||||||
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
@@ -91,8 +95,9 @@ interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a session from this workspace's account. Idempotent: an id not on
|
* Remove a session from this workspace's account. Idempotent: an id not on
|
||||||
* the account resolves without writing (decided on the domain write chain,
|
* the account resolves without writing, aside from the durable
|
||||||
* like attach). Never touches the session's own stored log.
|
* 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.
|
* @param sessionId - The session to remove.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend a session to this workspace's candidate account. An already
|
* 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};
|
* header cwd must resolve to an existing directory equal to {@link path};
|
||||||
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
||||||
* writing.
|
* writing.
|
||||||
@@ -82,7 +84,9 @@ interface Workspace {
|
|||||||
* with an anchor the session lands before it, without one it appends to the
|
* 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
|
* end. Only the moved id changes position. A session or anchor absent from
|
||||||
* the account rejects without writing; a move to the current position
|
* 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 sessionId - The accounted session to move.
|
||||||
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
@@ -91,8 +95,9 @@ interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a session from this workspace's account. Idempotent: an id not on
|
* Remove a session from this workspace's account. Idempotent: an id not on
|
||||||
* the account resolves without writing (decided on the domain write chain,
|
* the account resolves without writing, aside from the durable
|
||||||
* like attach). Never touches the session's own stored log.
|
* 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.
|
* @param sessionId - The session to remove.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -152,11 +152,11 @@ describe.skip('gen-cordis-catalog collectEvents', { timeout: 60_000 }, () => {
|
|||||||
|
|
||||||
it('accepts linked, foundation, generic-parameter, and explicitly exempt signature types', () => {
|
it('accepts linked, foundation, generic-parameter, and explicitly exempt signature types', () => {
|
||||||
const events = collectEvents(make(
|
const events = collectEvents(make(
|
||||||
' /**\n * Carry linked and foundation types.\n * @param value - the linked value.\n * @param assembly - assembly result documented outside the subsystems catalog.\n * @param signal - cancellation.\n * @mode parallel\n */\n \'fix/typed\'<T extends SessionEvent>(value: Readonly<T>, assembly: PromptAssembly, signal: AbortSignal): Promise<T>',
|
' /**\n * Carry linked and foundation types.\n * @param value - the linked value.\n * @param preset - deployment metadata documented outside the subsystems catalog.\n * @param signal - cancellation.\n * @mode parallel\n */\n \'fix/typed\'<T extends SessionEvent>(value: Readonly<T>, preset: PresetSpec, signal: AbortSignal): Promise<T>',
|
||||||
))
|
))
|
||||||
expect(events).toHaveLength(1)
|
expect(events).toHaveLength(1)
|
||||||
expect(renderEvents(events)).toContain('Types: [SessionEvent](../subsystems/core.md)')
|
expect(renderEvents(events)).toContain('Types: [SessionEvent](../subsystems/core.md)')
|
||||||
expect(renderEvents(events)).not.toContain('[PromptAssembly]')
|
expect(renderEvents(events)).not.toContain('[PresetSpec]')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('aggregates every unclassified signature type with its source and remediation', () => {
|
it('aggregates every unclassified signature type with its source and remediation', () => {
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write packages/workspace/workspace/README.md
|
# pnpm run verify-translation-pairing --write packages/workspace/workspace/README.md
|
||||||
README.md: 057765e38de9cc700210eb8edeb1ddc7ffc861ff
|
README.md: 057765e38de9cc700210eb8edeb1ddc7ffc861ff
|
||||||
README.zh.md: f3be9611eba45db3719ce76ad6625f4787fab9ac
|
README.zh.md: 7416875dbf2ee1652f6e1fa1663144d7407a1ae7
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ DeepSeek Harness 的 Workspace 实体注册表(`ctx.workspace`):通过领
|
|||||||
- `ctx.workspace.get(id)`/`list()`/`resolveByPath(path)`:由缓存提供的查找。`list()` 为同步操作,并遵循持久注册表顺序;`resolveByPath` 为异步操作,因为它采用相同的 `realpath` 规范化方式,并会拒绝缺失路径,而不是创建路径。
|
- `ctx.workspace.get(id)`/`list()`/`resolveByPath(path)`:由缓存提供的查找。`list()` 为同步操作,并遵循持久注册表顺序;`resolveByPath` 为异步操作,因为它采用相同的 `realpath` 规范化方式,并会拒绝缺失路径,而不是创建路径。
|
||||||
- `ctx.workspace.delete(id)`:只移除 Workspace 注册记录、对应的持久顺序条目及会话归属记录。未知 id 返回 `false`,成功移除记录则返回 `true`。目录、用户文件、活跃会话和持久化会话日志绝不受影响,因此相关会话会进入 Ungrouped。表写入失败时会恢复原顺序和此前发布的实体。
|
- `ctx.workspace.delete(id)`:只移除 Workspace 注册记录、对应的持久顺序条目及会话归属记录。未知 id 返回 `false`,成功移除记录则返回 `true`。目录、用户文件、活跃会话和持久化会话日志绝不受影响,因此相关会话会进入 Ungrouped。表写入失败时会恢复原顺序和此前发布的实体。
|
||||||
- `Workspace.attachSession(id)`:对照 workspace 路径验证实时或已持久化的会话头 cwd,并将新 id 前置。未知会话、缺失/无法解析/非目录的 cwd 值和不匹配情况都会在不写入的前提下被拒绝。`detachSession` 只移除候选索引条目。
|
- `Workspace.attachSession(id)`:对照 workspace 路径验证实时或已持久化的会话头 cwd,并将新 id 前置。未知会话、缺失/无法解析/非目录的 cwd 值和不匹配情况都会在不写入的前提下被拒绝。`detachSession` 只移除候选索引条目。
|
||||||
|
- `Workspace.insertSessionBefore(id, before?)`:在手动顺序内移动一个已记账的会话,语义类似 DOM 的 insertBefore:插到锚点之前,省略锚点则追加到末尾。会话或锚点不在记账中时拒绝且不写入;移动到当前位置时直接完成且不写入。Workspace 顺序绝不改变。
|
||||||
- `ctx.workspace.archiveSession(id)`/`archivedSessionIds`:覆盖在 workspace 记账之上的注册表级全局归档集合:被归档的会话从各分组视图中消失,但其会话日志和 `sessionIds` 席位保持不变,未来取消归档时可恢复原位置。归档接受任何实时或已持久化的会话(无论已记账还是 Ungrouped),对已归档的 id 直接完成而不写入,并拒绝未知 id。在该字段出现之前写入的状态解析为一个空集合。
|
- `ctx.workspace.archiveSession(id)`/`archivedSessionIds`:覆盖在 workspace 记账之上的注册表级全局归档集合:被归档的会话从各分组视图中消失,但其会话日志和 `sessionIds` 席位保持不变,未来取消归档时可恢复原位置。归档接受任何实时或已持久化的会话(无论已记账还是 Ungrouped),对已归档的 id 直接完成而不写入,并拒绝未知 id。在该字段出现之前写入的状态解析为一个空集合。
|
||||||
- `Workspace.sessionIds`:按持久候选顺序提供同步 id 加规范 cwd 成员投影。缺失头部、无效 cwd 值和不匹配情况都被过滤;下一次 workspace 变更会剪除它们。如果同一存储介质将一个会话索引到两个 workspace 下、用两条记录声明同一路径,或偏离持久 workspace 顺序,启动会被拒绝。
|
- `Workspace.sessionIds`:按持久候选顺序提供同步 id 加规范 cwd 成员投影。缺失头部、无效 cwd 值和不匹配情况都被过滤;下一次 workspace 变更会剪除它们。如果同一存储介质将一个会话索引到两个 workspace 下、用两条记录声明同一路径,或偏离持久 workspace 顺序,启动会被拒绝。
|
||||||
- `Workspace.status()`:未缓存的目录检查,返回 `'ok' | 'missing-dir'`;目录缺失绝不会改动记录。
|
- `Workspace.status()`:未缓存的目录检查,返回 `'ok' | 'missing-dir'`;目录缺失绝不会改动记录。
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ export interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend a session to this workspace's candidate account. An already
|
* 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};
|
* header cwd must resolve to an existing directory equal to {@link path};
|
||||||
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
* unknown ids, missing or invalid cwd values, and mismatches reject without
|
||||||
* writing.
|
* writing.
|
||||||
@@ -73,7 +75,9 @@ export interface Workspace {
|
|||||||
* with an anchor the session lands before it, without one it appends to the
|
* 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
|
* end. Only the moved id changes position. A session or anchor absent from
|
||||||
* the account rejects without writing; a move to the current position
|
* 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 sessionId - The accounted session to move.
|
||||||
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
* @param beforeSessionId - Accounted anchor to insert before; omitted appends.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
@@ -82,8 +86,9 @@ export interface Workspace {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a session from this workspace's account. Idempotent: an id not on
|
* Remove a session from this workspace's account. Idempotent: an id not on
|
||||||
* the account resolves without writing (decided on the domain write chain,
|
* the account resolves without writing, aside from the durable
|
||||||
* like attach). Never touches the session's own stored log.
|
* 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.
|
* @param sessionId - The session to remove.
|
||||||
* @returns resolution after durability.
|
* @returns resolution after durability.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,32 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* Generate the Cordis event and service catalogs from static declarations.
|
* Generate committed Cordis artifacts from the Typert catalog projector and
|
||||||
* The walk enforces event modes, JSDoc parameter/return completeness, and
|
* the independent vendored-core projector.
|
||||||
* signature type-link coverage; inherited Cordis services come from the
|
|
||||||
* curated table below. `--check` verifies both committed artifacts.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||||
import { dirname, resolve, sep } from 'node:path'
|
import { dirname, resolve } from 'node:path'
|
||||||
import ts from 'typescript'
|
import {
|
||||||
|
projectCordisCatalog,
|
||||||
|
renderEvents,
|
||||||
|
renderServices,
|
||||||
|
} from '@deepseek-ai/dsh-typert-generator'
|
||||||
|
import type { CordisCatalogPolicy } from '@deepseek-ai/dsh-typert-generator'
|
||||||
import { renderCordisCoreApiPages } from './cordis-core-api.ts'
|
import { renderCordisCoreApiPages } from './cordis-core-api.ts'
|
||||||
import { checkParams, checkReturns, parseJsDoc, parseTags, pointer, rawJsDoc, reportViolations, type Mode } from './jsdoc.ts'
|
|
||||||
import { cordisModuleBody, eventMembers, serviceClasses } from './cordis-walk.ts'
|
|
||||||
|
|
||||||
const root = resolve(import.meta.dirname, '..')
|
const root = resolve(import.meta.dirname, '..')
|
||||||
const OUT_EVENTS = 'docs/cordis-catalog/events.md'
|
const OUT_EVENTS = 'docs/cordis-catalog/events.md'
|
||||||
const OUT_SERVICES = 'docs/cordis-catalog/services.md'
|
const OUT_SERVICES = 'docs/cordis-catalog/services.md'
|
||||||
|
const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts'
|
||||||
|
|
||||||
/** The fenced-block info string for generated signature blocks (skipped by
|
/** One primary subsystems page per project type used by a generated signature. */
|
||||||
* doc-typecheck, since a bare signature fragment is not standalone-compilable). */
|
export const LINK_MAP: Readonly<Record<string, string>> = {
|
||||||
const FENCE = 'ts cordis-catalog'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* One primary subsystems page per project type used by a generated
|
|
||||||
* signature. This stays curated because union names intentionally do not
|
|
||||||
* reuse the type-equivalence manifest's map-symbol entries and some symbols
|
|
||||||
* appear on more than one page.
|
|
||||||
*/
|
|
||||||
export const LINK_MAP: Record<string, string> = {
|
|
||||||
Agent: 'core.md',
|
Agent: 'core.md',
|
||||||
AgentCancelCause: 'core.md',
|
AgentCancelCause: 'core.md',
|
||||||
AgentOptions: 'core.md',
|
AgentOptions: 'core.md',
|
||||||
@@ -35,6 +28,7 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
ContinuationDecision: 'core.md',
|
ContinuationDecision: 'core.md',
|
||||||
ContinuationStop: 'core.md',
|
ContinuationStop: 'core.md',
|
||||||
GenerateOptions: 'core.md',
|
GenerateOptions: 'core.md',
|
||||||
|
InboxItem: 'core.md',
|
||||||
InboxPlacement: 'core.md',
|
InboxPlacement: 'core.md',
|
||||||
MessageId: 'core.md',
|
MessageId: 'core.md',
|
||||||
HookContext: 'core.md',
|
HookContext: 'core.md',
|
||||||
@@ -57,9 +51,7 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
UserMessage: 'session.md',
|
UserMessage: 'session.md',
|
||||||
PreStepDecision: 'core.md',
|
PreStepDecision: 'core.md',
|
||||||
PreStepContext: 'core.md',
|
PreStepContext: 'core.md',
|
||||||
PromptDecision: 'core.md',
|
|
||||||
RequestErrorAction: 'core.md',
|
RequestErrorAction: 'core.md',
|
||||||
RequestError: 'core.md',
|
|
||||||
RequestFailureContext: 'core.md',
|
RequestFailureContext: 'core.md',
|
||||||
PreparedReferencedMessage: 'session-reference.md',
|
PreparedReferencedMessage: 'session-reference.md',
|
||||||
SessionReferenceCandidate: 'session-reference.md',
|
SessionReferenceCandidate: 'session-reference.md',
|
||||||
@@ -116,6 +108,7 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
PreparedLlmCall: 'llm-streaming.md',
|
PreparedLlmCall: 'llm-streaming.md',
|
||||||
LlmService: 'llm-streaming.md',
|
LlmService: 'llm-streaming.md',
|
||||||
StreamChunk: 'llm-streaming.md',
|
StreamChunk: 'llm-streaming.md',
|
||||||
|
SkillProviderControl: 'skills.md',
|
||||||
CreateSessionOptions: 'persistence.md',
|
CreateSessionOptions: 'persistence.md',
|
||||||
PrepareSessionOptions: 'persistence.md',
|
PrepareSessionOptions: 'persistence.md',
|
||||||
SessionHeader: 'persistence.md',
|
SessionHeader: 'persistence.md',
|
||||||
@@ -168,9 +161,11 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
SessionTitleObservationResult: 'session-query.md',
|
SessionTitleObservationResult: 'session-query.md',
|
||||||
SessionTitleProvider: 'session-title.md',
|
SessionTitleProvider: 'session-title.md',
|
||||||
SessionTitleSnapshot: 'session-title.md',
|
SessionTitleSnapshot: 'session-title.md',
|
||||||
|
SkillCatalogSnapshot: 'skills.md',
|
||||||
SkillDefinition: 'skills.md',
|
SkillDefinition: 'skills.md',
|
||||||
SkillLookupOptions: 'skills.md',
|
SkillLookupOptions: 'skills.md',
|
||||||
SkillProvider: 'skills.md',
|
SkillProvider: 'skills.md',
|
||||||
|
SkillProviderObservation: 'skills.md',
|
||||||
SkillRegistration: 'skills.md',
|
SkillRegistration: 'skills.md',
|
||||||
SkillSummary: 'skills.md',
|
SkillSummary: 'skills.md',
|
||||||
SaveTextSpill: 'spill.md',
|
SaveTextSpill: 'spill.md',
|
||||||
@@ -237,8 +232,6 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
WebSearchRequest: 'web.md',
|
WebSearchRequest: 'web.md',
|
||||||
WebSearchResult: 'web.md',
|
WebSearchResult: 'web.md',
|
||||||
WorkflowRun: 'workflow.md',
|
WorkflowRun: 'workflow.md',
|
||||||
WorkflowRunInfo: 'workflow.md',
|
|
||||||
WorkflowStartRequest: 'workflow.md',
|
|
||||||
PresetOption: 'permission.md',
|
PresetOption: 'permission.md',
|
||||||
PresetSpec: 'permission.md',
|
PresetSpec: 'permission.md',
|
||||||
InvariantInstaller: 'invariants.md',
|
InvariantInstaller: 'invariants.md',
|
||||||
@@ -253,14 +246,17 @@ export const LINK_MAP: Record<string, string> = {
|
|||||||
WorkspaceId: 'workspace.md',
|
WorkspaceId: 'workspace.md',
|
||||||
WebBootGraph: 'client-modules.md',
|
WebBootGraph: 'client-modules.md',
|
||||||
TelemetryRecord: 'telemetry.md',
|
TelemetryRecord: 'telemetry.md',
|
||||||
|
WorkflowRunInfo: 'workflow.md',
|
||||||
|
WorkflowStartRequest: 'workflow.md',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** TypeScript lib and pinned framework types that have no repository-owned data page. */
|
/** TypeScript lib and pinned framework types with no repository-owned data page. */
|
||||||
const FOUNDATION_TYPE_NAMES = new Set([
|
export const FOUNDATION_TYPE_NAMES: ReadonlySet<string> = new Set([
|
||||||
'AbortSignal',
|
'AbortSignal',
|
||||||
'AsyncIterable',
|
'AsyncIterable',
|
||||||
'Context',
|
'Context',
|
||||||
'Error',
|
'Error',
|
||||||
|
'Map',
|
||||||
'Partial',
|
'Partial',
|
||||||
'Pick',
|
'Pick',
|
||||||
'Promise',
|
'Promise',
|
||||||
@@ -269,7 +265,7 @@ const FOUNDATION_TYPE_NAMES = new Set([
|
|||||||
])
|
])
|
||||||
|
|
||||||
/** Project types deliberately documented outside the subsystems catalog. */
|
/** Project types deliberately documented outside the subsystems catalog. */
|
||||||
const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
||||||
AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md',
|
AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md',
|
||||||
z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
|
z: 'schemastery schema constructor is owned by vendor/schemastery (vendored upstream)',
|
||||||
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
BeginCommandRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts',
|
||||||
@@ -281,6 +277,7 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
|||||||
BashEnvVariableInfo: 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts',
|
BashEnvVariableInfo: 'service-local metadata type is owned by packages/bash/tool-bash/src/index.ts',
|
||||||
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
|
CompactAgentContext: 'compaction service input is owned by packages/compact/compact/src/index.ts',
|
||||||
ManualCompactAgentContext: 'manual compaction service input is owned by packages/compact/compact/src/index.ts',
|
ManualCompactAgentContext: 'manual compaction service input is owned by packages/compact/compact/src/index.ts',
|
||||||
|
DirectoryPickerCapability: 'picker interaction contract is owned by packages/host/directory-picker/README.md',
|
||||||
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
|
CreateAgentOptions: 'agent creation contract is owned by packages/core/agent/README.md',
|
||||||
DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
|
DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md',
|
||||||
ProjectionDefinition: 'projection unit contract is owned by packages/session/session-projection/README.md',
|
ProjectionDefinition: 'projection unit contract is owned by packages/session/session-projection/README.md',
|
||||||
@@ -318,401 +315,51 @@ const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
|
|||||||
WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Collect named references from parameter, generic-constraint/default, and return types. */
|
/** Repository data policy consumed by the Cordis catalog projector. */
|
||||||
function signatureTypeNames(member: ts.MethodSignature | ts.MethodDeclaration, sf: ts.SourceFile): string[] {
|
export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = {
|
||||||
const declared = new Set(member.typeParameters?.map(parameter => parameter.name.text) ?? [])
|
linkedTypePages: LINK_MAP,
|
||||||
const referenced = new Set<string>()
|
foundationTypeNames: FOUNDATION_TYPE_NAMES,
|
||||||
const visit = (node: ts.Node): void => {
|
typeLinkExemptions: TYPE_LINK_EXEMPTIONS,
|
||||||
if (ts.isTypeReferenceNode(node)) referenced.add(node.typeName.getText(sf))
|
inheritedEvents: [
|
||||||
if (ts.isTypeQueryNode(node)) referenced.add(node.exprName.getText(sf))
|
{ name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
|
||||||
ts.forEachChild(node, visit)
|
{ name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
|
||||||
}
|
{ name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:332' },
|
||||||
for (const parameter of member.typeParameters ?? []) {
|
{ name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:334' },
|
||||||
if (parameter.constraint) visit(parameter.constraint)
|
{ name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:336' },
|
||||||
if (parameter.default) visit(parameter.default)
|
{ name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:338' },
|
||||||
}
|
{ name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:340' },
|
||||||
for (const parameter of member.parameters) {
|
{ name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:342' },
|
||||||
if (parameter.type) visit(parameter.type)
|
{ name: 'hmr/change', summary: 'A watched source file changed on disk.', source: 'vendor/hmr/src/index.ts:20' },
|
||||||
}
|
{ name: 'hmr/reload', summary: 'Plugins are being reloaded after a change.', source: 'vendor/hmr/src/index.ts:21' },
|
||||||
if (member.type) visit(member.type)
|
{ name: 'exit', summary: 'The process is exiting on a signal.', source: 'vendor/loader/src/index.ts:23' },
|
||||||
return [...referenced].filter(name => !declared.has(name)).sort()
|
{ name: 'loader/config-update', summary: 'The loader config tree changed.', source: 'vendor/loader/src/index.ts:24' },
|
||||||
|
{ name: 'loader/entry-init', summary: 'A config entry is being initialized.', source: 'vendor/loader/src/index.ts:25' },
|
||||||
|
{ name: 'loader/partial-dispose', summary: 'An entry is being partially disposed on reload.', source: 'vendor/loader/src/index.ts:26' },
|
||||||
|
{ name: 'loader/patch-context', summary: 'A context is being patched during a reload.', source: 'vendor/loader/src/index.ts:27' },
|
||||||
|
],
|
||||||
|
inheritedServices: [
|
||||||
|
{ name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:34' },
|
||||||
|
{ name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).', source: 'vendor/cordis/src/events.ts:34' },
|
||||||
|
{ name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:164' },
|
||||||
|
{ name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.', source: 'vendor/cordis/src/fiber.ts:9' },
|
||||||
|
{ name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
|
||||||
|
{ name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
|
||||||
|
{ name: 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
|
||||||
|
{ name: 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
|
||||||
|
{ name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },
|
||||||
|
{ name: 'ctx.hmr', summary: 'The hot-module-reload watcher (present under the hmr plugin).', source: 'vendor/hmr/src/index.ts:15' },
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Append fail-closed signature type-link violations with actionable ownership choices. */
|
/** CLI entry: default writes every artifact; `--check` reports stale files.
|
||||||
function checkTypeLinks(
|
* @returns nothing; writes files or reports freshness through the process.
|
||||||
where: string,
|
|
||||||
member: ts.MethodSignature | ts.MethodDeclaration,
|
|
||||||
sf: ts.SourceFile,
|
|
||||||
violations: string[],
|
|
||||||
): void {
|
|
||||||
for (const name of signatureTypeNames(member, sf)) {
|
|
||||||
if (Object.hasOwn(LINK_MAP, name)
|
|
||||||
|| FOUNDATION_TYPE_NAMES.has(name)
|
|
||||||
|| Object.hasOwn(TYPE_LINK_EXEMPTIONS, name)) continue
|
|
||||||
violations.push(
|
|
||||||
`${where} references unclassified type '${name}'. Add it to LINK_MAP with its subsystems page, `
|
|
||||||
+ 'to FOUNDATION_TYPE_NAMES if TypeScript or Cordis owns it, or to TYPE_LINK_EXEMPTIONS with '
|
|
||||||
+ 'the non-catalog documentation owner.',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Throw one aggregated diagnostic for every unclassified signature type. */
|
|
||||||
function reportTypeLinkViolations(gate: string, violations: string[]): void {
|
|
||||||
if (violations.length === 0) return
|
|
||||||
throw new Error(
|
|
||||||
`${gate}: ${violations.length} signature type-link coverage violation(s):\n`
|
|
||||||
+ violations.map(violation => ` ${violation}`).join('\n'),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** One harness event, extracted from an `interface Events` block. */
|
|
||||||
interface EventEntry {
|
|
||||||
/** Scoped name, e.g. `agent/request`. */
|
|
||||||
name: string
|
|
||||||
/** The scope prefix, e.g. `agent` (everything before the first `/`). */
|
|
||||||
scope: string
|
|
||||||
/** Full signature text (the method-signature member, JSDoc stripped). */
|
|
||||||
signature: string
|
|
||||||
/** Original declaration JSDoc, dedented from its containing interface. */
|
|
||||||
jsDoc: string
|
|
||||||
/** Dispatch mode from the `@mode` tag. */
|
|
||||||
mode: Mode
|
|
||||||
/** Description prose (JSDoc minus the `@mode` tag), one line per paragraph. */
|
|
||||||
doc: string
|
|
||||||
/** Source pointer `packages/…/file.ts:line` of the declaration. */
|
|
||||||
source: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** One public service method and the source contract attached to it. */
|
|
||||||
interface ServiceMethodEntry {
|
|
||||||
/** Public method signature (body stripped). */
|
|
||||||
signature: string
|
|
||||||
/** Original method JSDoc, dedented from its containing class. */
|
|
||||||
jsDoc: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** One harness service, extracted from an `interface Context` block. */
|
|
||||||
interface ServiceEntry {
|
|
||||||
/** The `ctx.<key>` name, e.g. `llm`. */
|
|
||||||
key: string
|
|
||||||
/** The service class/interface name, e.g. `LlmService`. */
|
|
||||||
type: string
|
|
||||||
/** Whether the service class is abstract (a seam interface). */
|
|
||||||
abstract: boolean
|
|
||||||
/** Class-level JSDoc prose, one line per paragraph. */
|
|
||||||
doc: string
|
|
||||||
/** Public methods (bodies stripped), in source order. */
|
|
||||||
methods: ServiceMethodEntry[]
|
|
||||||
/** Source pointer of the class declaration. */
|
|
||||||
source: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A terse inherited-tier entry (pinned vendor surface). */
|
|
||||||
interface InheritedEntry {
|
|
||||||
name: string
|
|
||||||
summary: string
|
|
||||||
/** Source pointer `vendor/…:line`. */
|
|
||||||
source: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// cordisModuleBody / eventMembers / serviceClasses live in cordis-walk.ts.
|
|
||||||
|
|
||||||
/** The signature text of a method-signature member (everything but a body). */
|
|
||||||
function memberSignature(member: ts.TypeElement | ts.ClassElement, sf: ts.SourceFile): string {
|
|
||||||
const full = member.getText(sf)
|
|
||||||
const body = (member as { body?: ts.Node }).body
|
|
||||||
const sig = body ? full.slice(0, full.length - body.getText(sf).length) : full
|
|
||||||
return sig.replace(/\s*;?\s*$/, '').replace(/\s+/g, ' ').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy a node's original JSDoc while removing only the indentation imposed by
|
|
||||||
* its containing interface or class.
|
|
||||||
*/
|
*/
|
||||||
function jsDocText(text: string, sf: ts.SourceFile, node: ts.Node): string {
|
export function main(): void {
|
||||||
const raw = rawJsDoc(text, node)
|
const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY)
|
||||||
if (!raw) return ''
|
|
||||||
const start = text.lastIndexOf(raw, node.getStart(sf))
|
|
||||||
const { line } = sf.getLineAndCharacterOfPosition(start)
|
|
||||||
const lineStart = sf.getPositionOfLineAndCharacter(line, 0)
|
|
||||||
const indent = text.slice(lineStart, start)
|
|
||||||
return raw.split('\n')
|
|
||||||
.map((lineText, index) => index > 0 && lineText.startsWith(indent) ? lineText.slice(indent.length) : lineText)
|
|
||||||
.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Walk every harness `interface Events` block and extract its events, hard-
|
|
||||||
* erroring (aggregated) on any JSDoc-completeness violation: a missing/
|
|
||||||
* contradicted `@mode`, missing description prose, or an undocumented payload
|
|
||||||
* parameter. `scanRoot` defaults to the repo root; tests pass a fixture dir. */
|
|
||||||
export function collectEvents(scanRoot: string = root): EventEntry[] {
|
|
||||||
const entries: EventEntry[] = []
|
|
||||||
const violations: string[] = []
|
|
||||||
const typeLinkViolations: string[] = []
|
|
||||||
for (const rel of globSync('packages/*/*/src/*.ts', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) {
|
|
||||||
const abs = resolve(scanRoot, rel)
|
|
||||||
const text = readFileSync(abs, 'utf8')
|
|
||||||
if (!text.includes('interface Events')) continue
|
|
||||||
const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true)
|
|
||||||
const body = cordisModuleBody(sf)
|
|
||||||
if (!body) continue
|
|
||||||
for (const { name, member } of eventMembers(body, sf)) {
|
|
||||||
const signature = memberSignature(member, sf)
|
|
||||||
const raw = rawJsDoc(text, member)
|
|
||||||
const { doc, mode } = parseJsDoc(raw)
|
|
||||||
const src = pointer(rel, sf, member)
|
|
||||||
const where = `event '${name}' (${src})`
|
|
||||||
checkTypeLinks(where, member, sf, typeLinkViolations)
|
|
||||||
if (!mode) {
|
|
||||||
violations.push(`${where} is missing an @mode tag. Add '@mode emit|waterfall|parallel|serial|bail' to its JSDoc (see AGENTS.md).`)
|
|
||||||
}
|
|
||||||
// Conclusive structural check: a trailing `next: () => …` parameter is a
|
|
||||||
// waterfall. (emit vs parallel vs serial is not structurally
|
|
||||||
// distinguishable, so it is trusted from the tag.)
|
|
||||||
const last = member.parameters.at(-1)
|
|
||||||
const hasNext = !!last && last.name.getText(sf) === 'next'
|
|
||||||
if (mode && hasNext && mode !== 'waterfall') {
|
|
||||||
violations.push(`${where} has a trailing 'next' parameter (structurally a waterfall) but is tagged '@mode ${mode}'. Fix the tag or the signature.`)
|
|
||||||
}
|
|
||||||
if (mode && !hasNext && mode === 'waterfall') {
|
|
||||||
violations.push(`${where} is tagged '@mode waterfall' but has no trailing 'next' parameter. A waterfall delegates via next().`)
|
|
||||||
}
|
|
||||||
if (!doc) violations.push(`${where} has no description prose. Say what happened / what a listener may do, above the block tags.`)
|
|
||||||
// Payload parameters need a non-empty @param. The `this` receiver is not
|
|
||||||
// payload, and a waterfall's trailing `next` is covered by its mode.
|
|
||||||
const { params } = parseTags(raw)
|
|
||||||
checkParams(where, 'event', member.parameters, params, sf,
|
|
||||||
p => (ts.isIdentifier(p.name) && p.name.text === 'this') || (hasNext && p === last), violations)
|
|
||||||
if (mode) entries.push({ name, scope: name.split('/')[0] ?? name, signature, jsDoc: jsDocText(text, sf, member), mode, doc, source: src })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reportViolations('gen-cordis-catalog', violations)
|
|
||||||
reportTypeLinkViolations('gen-cordis-catalog', typeLinkViolations)
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Walk every harness `interface Context` block + its service class, hard-
|
|
||||||
* erroring (aggregated) on any JSDoc-completeness violation: a class or public
|
|
||||||
* method without JSDoc prose, an undocumented parameter, a stale `@param`, a
|
|
||||||
* missing `@returns` on a non-void method, or an inferred (unannotated) return
|
|
||||||
* type the pure-AST walk cannot classify.
|
|
||||||
* `scanRoot` defaults to the repo root; tests pass a fixture dir. */
|
|
||||||
export function collectServices(scanRoot: string = root): ServiceEntry[] {
|
|
||||||
const entries: ServiceEntry[] = []
|
|
||||||
const violations: string[] = []
|
|
||||||
const typeLinkViolations: string[] = []
|
|
||||||
for (const rel of globSync('packages/*/*/src/index.ts', { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) {
|
|
||||||
const abs = resolve(scanRoot, rel)
|
|
||||||
const text = readFileSync(abs, 'utf8')
|
|
||||||
if (!text.includes('interface Context')) continue
|
|
||||||
const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true)
|
|
||||||
const body = cordisModuleBody(sf)
|
|
||||||
if (!body) continue
|
|
||||||
// Resolve each ctx key to its service class (shared walk) and emit an entry.
|
|
||||||
for (const { key, type, cls, abstract, doc: clsDoc } of serviceClasses(body, sf, rel, violations)) {
|
|
||||||
const methods: ServiceMethodEntry[] = []
|
|
||||||
for (const member of cls.members) {
|
|
||||||
if (!ts.isMethodDeclaration(member)) continue
|
|
||||||
// Only instance methods callable through `ctx.<key>` are surface;
|
|
||||||
// private, protected, and static methods are not.
|
|
||||||
const nonPublic = member.modifiers?.some(m =>
|
|
||||||
m.kind === ts.SyntaxKind.PrivateKeyword
|
|
||||||
|| m.kind === ts.SyntaxKind.ProtectedKeyword
|
|
||||||
|| m.kind === ts.SyntaxKind.StaticKeyword)
|
|
||||||
|| ts.isPrivateIdentifier(member.name)
|
|
||||||
if (nonPublic) continue
|
|
||||||
const memberName = member.name.getText(sf)
|
|
||||||
if (memberName.startsWith('[')) continue // computed/symbol members
|
|
||||||
const where = `service method ctx.${key}.${memberName} (${pointer(rel, sf, member)})`
|
|
||||||
checkTypeLinks(where, member, sf, typeLinkViolations)
|
|
||||||
const raw = rawJsDoc(text, member)
|
|
||||||
methods.push({ signature: memberSignature(member, sf), jsDoc: jsDocText(text, sf, member) })
|
|
||||||
if (!raw) { violations.push(`${where} has no JSDoc.`); continue }
|
|
||||||
if (!parseJsDoc(raw).doc) violations.push(`${where} has no description prose above its block tags.`)
|
|
||||||
const { params, returns } = parseTags(raw)
|
|
||||||
// Every parameter needs a non-empty @param (`this` receiver exempt),
|
|
||||||
// and a non-void ANNOTATED result needs a non-empty @returns — the
|
|
||||||
// shared checkers carry the exact contract.
|
|
||||||
checkParams(where, 'service', member.parameters, params, sf,
|
|
||||||
p => ts.isIdentifier(p.name) && p.name.text === 'this', violations)
|
|
||||||
checkReturns(where, member.type, returns, sf, violations)
|
|
||||||
}
|
|
||||||
entries.push({
|
|
||||||
key,
|
|
||||||
type,
|
|
||||||
abstract,
|
|
||||||
doc: clsDoc,
|
|
||||||
methods,
|
|
||||||
source: pointer(rel, sf, cls),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reportViolations('gen-cordis-catalog', violations)
|
|
||||||
reportTypeLinkViolations('gen-cordis-catalog', typeLinkViolations)
|
|
||||||
return entries.sort((a, b) => a.key.localeCompare(b.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The inherited tier — cordis core + loader/hmr/timer. Curated, terse, and
|
|
||||||
* hand-summarized because (a) it is pinned vendor source that changes only on a
|
|
||||||
* deliberate vendor sync, (b) the cordis-core `Context` mixes true ctx members
|
|
||||||
* with non-service fields (`root`, `baseUrl`, `logger`) that a blind walk would
|
|
||||||
* wrongly surface as services, and (c) the internal/* events carry no JSDoc to
|
|
||||||
* render. Source pointers are verified against vendor by `verify-md-links`'
|
|
||||||
* sibling check is N/A; keep them current on a vendor bump.
|
|
||||||
*/
|
|
||||||
const INHERITED_EVENTS: InheritedEntry[] = [
|
|
||||||
{ name: 'internal/plugin', summary: 'A plugin fiber was created.', source: 'vendor/cordis/src/events.ts:328' },
|
|
||||||
{ name: 'internal/status', summary: 'A fiber changed lifecycle state.', source: 'vendor/cordis/src/events.ts:330' },
|
|
||||||
{ name: 'internal/service', summary: 'Interception hook for a service binding (no core producer).', source: 'vendor/cordis/src/events.ts:332' },
|
|
||||||
{ name: 'internal/update', summary: 'Waterfall: a fiber config update is being applied.', source: 'vendor/cordis/src/events.ts:334' },
|
|
||||||
{ name: 'internal/get', summary: 'Waterfall: a service is being read from the store.', source: 'vendor/cordis/src/events.ts:336' },
|
|
||||||
{ name: 'internal/set', summary: 'Waterfall: a service is being written to the store.', source: 'vendor/cordis/src/events.ts:338' },
|
|
||||||
{ name: 'internal/listener', summary: 'A listener was registered.', source: 'vendor/cordis/src/events.ts:340' },
|
|
||||||
{ name: 'internal/dispatch', summary: 'An event is being dispatched to listeners.', source: 'vendor/cordis/src/events.ts:342' },
|
|
||||||
{ name: 'hmr/change', summary: 'A watched source file changed on disk.', source: 'vendor/hmr/src/index.ts:20' },
|
|
||||||
{ name: 'hmr/reload', summary: 'Plugins are being reloaded after a change.', source: 'vendor/hmr/src/index.ts:21' },
|
|
||||||
{ name: 'exit', summary: 'The process is exiting on a signal.', source: 'vendor/loader/src/index.ts:23' },
|
|
||||||
{ name: 'loader/config-update', summary: 'The loader config tree changed.', source: 'vendor/loader/src/index.ts:24' },
|
|
||||||
{ name: 'loader/entry-init', summary: 'A config entry is being initialized.', source: 'vendor/loader/src/index.ts:25' },
|
|
||||||
{ name: 'loader/partial-dispose', summary: 'An entry is being partially disposed on reload.', source: 'vendor/loader/src/index.ts:26' },
|
|
||||||
{ name: 'loader/patch-context', summary: 'A context is being patched during a reload.', source: 'vendor/loader/src/index.ts:27' },
|
|
||||||
]
|
|
||||||
|
|
||||||
export const INHERITED_SERVICES: InheritedEntry[] = [
|
|
||||||
{ name: 'ctx.on / ctx.once', summary: 'Register an event listener (disposable).', source: 'vendor/cordis/src/events.ts:34' },
|
|
||||||
{ name: 'ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall', summary: 'Dispatch an event (sync / awaited / first-bail / veto-chain).', source: 'vendor/cordis/src/events.ts:34' },
|
|
||||||
{ name: 'ctx.plugin / ctx.inject', summary: 'Load a plugin / declare required services.', source: 'vendor/cordis/src/registry.ts:164' },
|
|
||||||
{ name: 'ctx.effect', summary: 'Register a disposable side effect tied to the fiber.', source: 'vendor/cordis/src/fiber.ts:9' },
|
|
||||||
{ name: 'ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin', summary: 'Low-level service-store access and binding.', source: 'vendor/cordis/src/reflect.ts:7' },
|
|
||||||
{ name: 'ctx.extend / ctx.isolate / ctx.intercept', summary: 'Derive a child context (scoped services / isolation / interception).', source: 'vendor/cordis/src/context.ts:42' },
|
|
||||||
{ name: 'ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger', summary: 'Ambient handles onto the running context graph.', source: 'vendor/cordis/src/context.ts:16' },
|
|
||||||
{ name: 'ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)', summary: 'Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick).', source: 'vendor/timer/src/index.ts:4' },
|
|
||||||
{ name: 'ctx.loader', summary: 'The config Loader that booted the app (present under the loader).', source: 'vendor/loader/src/index.ts:30' },
|
|
||||||
{ name: 'ctx.hmr', summary: 'The hot-module-reload watcher (present under the hmr plugin).', source: 'vendor/hmr/src/index.ts:15' },
|
|
||||||
]
|
|
||||||
|
|
||||||
/** Render the cross-link "Types:" line for a signature, or '' if none apply. */
|
|
||||||
function typeLinks(signature: string): string {
|
|
||||||
const seen = new Set<string>()
|
|
||||||
for (const name of Object.keys(LINK_MAP)) {
|
|
||||||
if (new RegExp(`\\b${name}\\b`).test(signature)) seen.add(name)
|
|
||||||
}
|
|
||||||
if (seen.size === 0) return ''
|
|
||||||
const links = [...seen].sort().map(n => `[${n}](../subsystems/${LINK_MAP[n]})`)
|
|
||||||
return `Types: ${links.join(' · ')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Render one harness event entry. */
|
|
||||||
function renderEvent(e: EventEntry): string[] {
|
|
||||||
const out = [`### \`${e.name}\` — ${e.mode}`, '']
|
|
||||||
if (e.doc) out.push(e.doc, '')
|
|
||||||
out.push('```' + FENCE, e.jsDoc, e.signature, '```', '')
|
|
||||||
const links = typeLinks(e.signature)
|
|
||||||
if (links) out.push(links, '')
|
|
||||||
out.push(`Source: [\`${e.source}\`](../../${e.source.split(':')[0]})`, '')
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Render one harness service entry. */
|
|
||||||
function renderService(s: ServiceEntry): string[] {
|
|
||||||
const kind = s.abstract ? ' (abstract seam)' : ''
|
|
||||||
const out = [`## \`ctx.${s.key}\` — \`${s.type}\`${kind}`, '']
|
|
||||||
if (s.doc) out.push(s.doc, '')
|
|
||||||
if (s.methods.length) {
|
|
||||||
const declarations = s.methods.flatMap((method, index) => [
|
|
||||||
...(index > 0 ? [''] : []),
|
|
||||||
method.jsDoc,
|
|
||||||
method.signature,
|
|
||||||
])
|
|
||||||
out.push('```' + FENCE, ...declarations, '```', '')
|
|
||||||
const links = typeLinks(s.methods.map(method => method.signature).join('\n'))
|
|
||||||
if (links) out.push(links, '')
|
|
||||||
}
|
|
||||||
out.push(`Source: [\`${s.source}\`](../../${s.source.split(':')[0]})`, '')
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The shared generated-file banner comment. */
|
|
||||||
const BANNER = [
|
|
||||||
'<!-- Generated by scripts/gen-cordis-catalog.ts — do not edit by hand.',
|
|
||||||
' Run `pnpm run gen-cordis-catalog` to regenerate. -->',
|
|
||||||
'',
|
|
||||||
]
|
|
||||||
|
|
||||||
/** The shared GENERATED + freshness-gate + fence notice paragraph. */
|
|
||||||
const GATE_NOTICE = 'This file is GENERATED from source (`scripts/gen-cordis-catalog.ts`) and verified fresh by `pnpm run verify-cordis-catalog` (part of `doc-sync`) — do not edit it by hand. Signature blocks use a `ts cordis-catalog` fence and include the original source JSDoc immediately before each event or service method. doc-typecheck skips these bare declaration fragments; type names in a signature link to the page that documents them.'
|
|
||||||
|
|
||||||
/** Render the events catalog (pure, deterministic given sorted inputs). */
|
|
||||||
export function renderEvents(events: EventEntry[]): string {
|
|
||||||
const lines: string[] = [
|
|
||||||
...BANNER,
|
|
||||||
'# Cordis Events Catalog',
|
|
||||||
'',
|
|
||||||
'Every cordis event a plugin can listen to: exact signature, dispatch mode, and original declaration JSDoc. This is one axis of the **wiring** reference a plugin author works against — the callable `ctx.<key>` surface is the sibling [services catalog](services.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around.',
|
|
||||||
'',
|
|
||||||
GATE_NOTICE,
|
|
||||||
'',
|
|
||||||
'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns, grouped by scope. The **inherited tier** at the end is the cordis-core + loader/hmr/timer event surface a plugin also sees — pinned vendor source, summarized tersely. The event-dispatch methods themselves are generated in the [Cordis core Events API](core/events.md).',
|
|
||||||
'',
|
|
||||||
'Dispatch modes: **emit** (fire-and-forget), **waterfall** (each listener gets `next()` and may transform or veto — see [waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)), **parallel** (awaited fan-out; all listeners run), **serial** (awaited in registration order until one returns a bail value — anything other than `null`, `false`, or `undefined`), **bail** (synchronous in-order dispatch until one listener returns a bail value; the scoped input-mutation events use it for an applied/not-applied answer).',
|
|
||||||
'',
|
|
||||||
]
|
|
||||||
const scopes = [...new Set(events.map(e => e.scope))].sort()
|
|
||||||
for (const scope of scopes) {
|
|
||||||
lines.push(`## \`${scope}/*\``, '')
|
|
||||||
for (const e of events.filter(x => x.scope === scope).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
||||||
lines.push(...renderEvent(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.push(
|
|
||||||
'## Inherited events (cordis core + loader/hmr/timer)',
|
|
||||||
'',
|
|
||||||
'The framework events every plugin also sees, beyond the harness vocabulary above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of the event bus, without elevating framework internals to the harness tier\'s prominence.',
|
|
||||||
'',
|
|
||||||
)
|
|
||||||
for (const e of INHERITED_EVENTS) {
|
|
||||||
lines.push(`- \`${e.name}\` — ${e.summary} ([\`${e.source}\`](../../${e.source.split(':')[0]}))`)
|
|
||||||
}
|
|
||||||
lines.push('')
|
|
||||||
return lines.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Render the services catalog (pure, deterministic given sorted inputs). */
|
|
||||||
export function renderServices(services: ServiceEntry[]): string {
|
|
||||||
const lines: string[] = [
|
|
||||||
...BANNER,
|
|
||||||
'# Cordis Services Catalog',
|
|
||||||
'',
|
|
||||||
'Every `ctx.<key>` service a plugin can call: the exact public interface with original method JSDoc, plus the class JSDoc. This is one axis of the **wiring** reference a plugin author works against — the events a plugin listens to are the sibling [events catalog](events.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around. An abstract seam (e.g. `ctx.bash`) is implemented by a separate package; the interface is what consumers code against.',
|
|
||||||
'',
|
|
||||||
GATE_NOTICE,
|
|
||||||
'',
|
|
||||||
'The **harness tier** below (the `@deepseek-ai/dsh-*` packages) is the vocabulary this repo owns. The **inherited tier** at the end is the cordis-core + loader/hmr/timer `ctx` surface a plugin also sees — pinned vendor source, summarized tersely. Detailed Context, Fiber, Registry, and Service APIs are generated in the [Cordis core API](core/context.md).',
|
|
||||||
'',
|
|
||||||
]
|
|
||||||
for (const s of services) lines.push(...renderService(s))
|
|
||||||
lines.push(
|
|
||||||
'## Inherited `ctx` members (cordis core + loader/hmr/timer)',
|
|
||||||
'',
|
|
||||||
'The framework `ctx` surface every plugin also sees, beyond the harness services above. This is pinned vendor source ([vendoring policy](../../vendor/README.md)); it is summarized here so the page is a complete picture of what `ctx` offers, without elevating framework internals to the harness tier\'s prominence.',
|
|
||||||
'',
|
|
||||||
)
|
|
||||||
for (const s of INHERITED_SERVICES) {
|
|
||||||
lines.push(`- \`${s.name}\` — ${s.summary} ([\`${s.source}\`](../../${s.source.split(':')[0]}))`)
|
|
||||||
}
|
|
||||||
lines.push('')
|
|
||||||
return lines.join('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** CLI entry: `--write` (default) writes both catalogs, `--check` fails if
|
|
||||||
* either is stale. Guarded behind an entry-point check so importing this module
|
|
||||||
* for tests neither regenerates the committed files nor calls process.exit. */
|
|
||||||
function main(): void {
|
|
||||||
const outputs: [string, string][] = [
|
const outputs: [string, string][] = [
|
||||||
[OUT_EVENTS, renderEvents(collectEvents())],
|
[OUT_EVENTS, renderEvents([...model.events], CORDIS_CATALOG_POLICY)],
|
||||||
[OUT_SERVICES, renderServices(collectServices())],
|
[OUT_SERVICES, renderServices([...model.services], CORDIS_CATALOG_POLICY)],
|
||||||
|
[OUT_RUNTIME_API, projector.renderRuntimeApi(model)],
|
||||||
...renderCordisCoreApiPages(),
|
...renderCordisCoreApiPages(),
|
||||||
]
|
]
|
||||||
if (process.argv.includes('--check')) {
|
if (process.argv.includes('--check')) {
|
||||||
@@ -722,9 +369,7 @@ function main(): void {
|
|||||||
try {
|
try {
|
||||||
committed = readFileSync(resolve(root, out), 'utf8')
|
committed = readFileSync(resolve(root, out), 'utf8')
|
||||||
} catch {
|
} catch {
|
||||||
// Only ENOENT (not yet generated) is expected; a present-but-unreadable
|
// Only ENOENT is expected; either read failure has the same remedy.
|
||||||
// file is not a state this repo produces. Either way the remedy is the
|
|
||||||
// same — regenerate — so treat a read failure as "stale".
|
|
||||||
committed = null
|
committed = null
|
||||||
}
|
}
|
||||||
if (committed !== content) stale.push(out)
|
if (committed !== content) stale.push(out)
|
||||||
@@ -745,7 +390,4 @@ function main(): void {
|
|||||||
console.log(`gen-cordis-catalog: wrote ${outputs.length} generated file(s).`)
|
console.log(`gen-cordis-catalog: wrote ${outputs.length} generated file(s).`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run only when invoked as a script, not when imported by a test.
|
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) main()
|
||||||
if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) {
|
|
||||||
main()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@
|
|||||||
{
|
{
|
||||||
"doc": "docs/subsystems/goal.md",
|
"doc": "docs/subsystems/goal.md",
|
||||||
"symbol": "GoalView",
|
"symbol": "GoalView",
|
||||||
"source": "packages/goal/goal/src/domain.ts"
|
"source": "packages/goal/goal/src/types.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/goal.md",
|
"doc": "docs/subsystems/goal.md",
|
||||||
@@ -219,12 +219,12 @@
|
|||||||
{
|
{
|
||||||
"doc": "docs/subsystems/goal.md",
|
"doc": "docs/subsystems/goal.md",
|
||||||
"symbol": "CreateGoalRequest",
|
"symbol": "CreateGoalRequest",
|
||||||
"source": "packages/goal/goal/src/domain.ts"
|
"source": "packages/goal/goal/src/types.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/goal.md",
|
"doc": "docs/subsystems/goal.md",
|
||||||
"symbol": "EditGoalRequest",
|
"symbol": "EditGoalRequest",
|
||||||
"source": "packages/goal/goal/src/domain.ts"
|
"source": "packages/goal/goal/src/types.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/goal.md",
|
"doc": "docs/subsystems/goal.md",
|
||||||
@@ -1508,7 +1508,7 @@
|
|||||||
{
|
{
|
||||||
"doc": "docs/subsystems/permission.md",
|
"doc": "docs/subsystems/permission.md",
|
||||||
"symbol": "PresetOption",
|
"symbol": "PresetOption",
|
||||||
"source": "packages/interaction/permission/src/index.ts"
|
"source": "packages/interaction/permission/src/types.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/plan.md",
|
"doc": "docs/subsystems/plan.md",
|
||||||
@@ -1575,31 +1575,6 @@
|
|||||||
"symbol": "DomainChanged",
|
"symbol": "DomainChanged",
|
||||||
"source": "packages/storage/storage-domain/src/events.ts"
|
"source": "packages/storage/storage-domain/src/events.ts"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"doc": "docs/subsystems/tui.md",
|
|
||||||
"symbol": "TuiOverlayRequest",
|
|
||||||
"source": "packages/interaction/tui/src/extension.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doc": "docs/subsystems/tui.md",
|
|
||||||
"symbol": "TuiOverlayHost",
|
|
||||||
"source": "packages/interaction/tui/src/extension.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doc": "docs/subsystems/tui.md",
|
|
||||||
"symbol": "TuiOverlaySession",
|
|
||||||
"source": "packages/interaction/tui/src/extension.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doc": "docs/subsystems/tui.md",
|
|
||||||
"symbol": "TuiOverlayCloseReason",
|
|
||||||
"source": "packages/interaction/tui/src/extension.ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doc": "docs/subsystems/tui.md",
|
|
||||||
"symbol": "TuiOverlayOutcome",
|
|
||||||
"source": "packages/interaction/tui/src/extension.ts"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/workspace.md",
|
"doc": "docs/subsystems/workspace.md",
|
||||||
"symbol": "WorkspaceId",
|
"symbol": "WorkspaceId",
|
||||||
@@ -1623,17 +1598,17 @@
|
|||||||
{
|
{
|
||||||
"doc": "docs/subsystems/telemetry.md",
|
"doc": "docs/subsystems/telemetry.md",
|
||||||
"symbol": "TelemetrySeverity",
|
"symbol": "TelemetrySeverity",
|
||||||
"source": "packages/telemetry/session-telemetry/src/index.ts"
|
"source": "packages/session/session-telemetry/src/index.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/telemetry.md",
|
"doc": "docs/subsystems/telemetry.md",
|
||||||
"symbol": "TelemetryRecord",
|
"symbol": "TelemetryRecord",
|
||||||
"source": "packages/telemetry/session-telemetry/src/index.ts"
|
"source": "packages/session/session-telemetry/src/index.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/telemetry.md",
|
"doc": "docs/subsystems/telemetry.md",
|
||||||
"symbol": "TelemetryBackend",
|
"symbol": "TelemetryBackend",
|
||||||
"source": "packages/telemetry/session-telemetry/src/index.ts"
|
"source": "packages/session/session-telemetry/src/index.ts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doc": "docs/subsystems/typert.md",
|
"doc": "docs/subsystems/typert.md",
|
||||||
|
|||||||
Reference in New Issue
Block a user