From 645fcf57137a82000f115aaabb7500b8682d6391 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:53:05 +0800 Subject: [PATCH 01/19] docs(agent-note): propose packages/ regrouping by measured clustering RFC settling the target group map: fold the session family into one session/ group, dissolve ui/ into interaction/ (+tui), boot/, and the scaffold/ developer-tooling family (absorbing jsonrpc as server), merge timeout/ into guard/, and rename cordis/ to self-modification/. Zero npm renames now; five FIXME-tracked intended renames. Squash of the reviewed RFC iterations (turtle1999 map corrections applied). --- .../2026-07-29-package-regrouping.i18n.yaml | 6 + .../2026-07-29-package-regrouping.md | 106 ++++++++++++++++++ .../2026-07-29-package-regrouping.zh.md | 106 ++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md create mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml new file mode 100644 index 0000000000..cdd772386f --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md +2026-07-29-package-regrouping.md: 9a56f9f5f1e0aef7d1f9efe60168ec1b0f41cce8 +2026-07-29-package-regrouping.zh.md: c49c5a1daffaff599f70f36b62e4dd3b5de3e5cf diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md new file mode 100644 index 0000000000..9a56f9f5f1 --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md @@ -0,0 +1,106 @@ +# Agent Note: Regroup packages/ by measured clustering + +Status: proposed + +English | [中文](2026-07-29-package-regrouping.zh.md) + +## Problem + +The two-level `packages//` hierarchy ([original decision](../../archived/architecture/2026-06-20-package-hierarchy.md)) has drifted since June: 167 packages now sit in 40 groups, and several group boundaries no longer match how the packages actually cluster. + +- `ui/` mixes four unrelated planes: the human terminal channel (`tui`), the SDK's JSON-RPC server half (`jsonrpc`, whose peer dependency on `dsh-sdk-protocol` binds it to the `sdk/` stack), the human-interaction seams (`user-interaction`, `user-approval`, `permission`, `tool-ask-user`, `commands`), and channel-neutral boot glue (`app-boot`). Its own README narrates the mixture instead of stating a role. +- The session family is fragmented across five groups — `session-persistence/`, `session-projection/`, `session-query/`, `session-title/`, and `telemetry/` — although the measured dependency edges tie them together (query → persistence, title → projection, projection → persistence; see [docs/module-graph.md](../../../../docs/module-graph.md)). +- Two group names collide with unrelated packages: `telemetry/` (session reporting) vs `sdk/telemetry` = `dsh-telemetry` (launcher-side SDK telemetry), and `timeout/` (a tool-call guard) vs `util/timeout` (the generic promise utility). +- `cordis/` names its group after the framework every package is built on, so the name discriminates nothing; its single package `tool-cordis` is the runtime self-modification toolset. +- `sdk/` folder names are inconsistent: `sdk/sdk-client` and `sdk/sdk-protocol` repeat the group name while `sdk/telemetry`, `sdk/helper`, and `sdk/scripts` do not. + +The north star for the regrouping: **closely clustered packages share a group.** A cluster is measured — peer-dependency edges and co-change — not thematic. An isolated seam family may stand alone as a small group; the failure mode to avoid is the grab-bag whose name describes no single role. + +## Proposal + +Recompose six groups; every other group keeps today's boundary and contents (the dependency analysis confirmed the capability families — `bash/`, `pty/`, `code-runtime/`, `sandbox/`, `subprocess/`, `fs/`, `lsp/`, `web/`, `skill/`, and the rest — were already drawn correctly). npm package names do not change in this reorganization; the folder tree carries the whole change. + +| Group | Members (folder names) | From | +|---|---|---| +| `session/` | session-persistence, session-persistence-jsonl, session-persistence-sqlite, session-checkpoint-policy, session-projection, session-projection-cache, session-title, session-title-llm, session-title-first-message-llm, session-title-all-messages-llm, session-telemetry, session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` | +| `interaction/` | user-interaction, user-approval, permission, tool-ask-user, commands, tui | `ui/` | +| `boot/` | app-boot | `ui/` | +| `scaffold/` | helper, scripts, create-sdk, protocol, client, server, telemetry | `sdk/` + `ui/jsonrpc` | +| `guard/` | repeat-tool-guard, timeout-policy | `guard/` + `timeout/` | +| `self-modification/` | tool-cordis | `cordis/` | + +- **`session/`** is the durable session data plane: the persistence seam with its backends and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (review: prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals. Absorbing `telemetry/` ends the group-name collision with `dsh-telemetry`. +- **`interaction/`** is the human-collaboration plane plus the terminal channel that answers it: the question/approval seams, the permission preset, the model-facing `ask_user_question` tool, the human-command registry (`plan-mode` and `command-goal` already consume `commands` together with the interaction seams), and `tui` — the interactive channel is the plane's richest provider and consumer (peer edges to `commands` and `user-interaction`), and a one-package `tui/` group would spend a top-level name on one plugin. +- **`boot/`** is a role-complete single-package group: the shared bin boot glue that belongs to no channel and no assembly (consumed by `apps/cli`, the `scaffold/` launcher, and the `examples/` demo bins). +- **`scaffold/`** is the developer-tooling family: project helper, launcher, initializer, wire protocol with both ends (`server` is today's `ui/jsonrpc`), and launcher telemetry. Renamed from `sdk/` in review: the whole `packages/` tree *is* the SDK, so a group named `sdk/` inside it says nothing; `scaffold/` names the create/launch/drive-a-project role. Folders drop the legacy `sdk-` prefix (`protocol`, `client`, `server`), matching the `client/`/`host/` role-named folder style. +- **`guard/`** keeps its documented role, loop-hygiene guards, and gains the tool-call timeout enforcer, dissolving the one-package `timeout/` group whose name collided with `util/timeout`. +- **`self-modification/`** names the role `cordis/` obscured: the toolset with which the agent inspects and mounts plugins in its own live runtime, and the landing zone for future self-modification packages. + +40 groups become 37; the win is clustering correctness and truthful names, not count. + +## Deferred renames (FIXME markers) + +Five npm names should eventually change, but renaming inside the reorganization would turn a pure-move PR into an import-churn PR. Instead, the implementation adds a `FIXME` to each affected package's module JSDoc naming the intended new name. `FIXME` blocks a tagged release ([marker semantics](../../../../docs/development.md)), which is the wanted forcing function: these renames are only free while nothing external consumes the packages. + +| Current npm name | Intended name | Why | +|---|---|---| +| `@deepseek-ai/dsh-jsonrpc` | `@deepseek-ai/dsh-sdk-server` | Names the wire encoding, not the role; it is the server half of the SDK protocol | +| `@deepseek-ai/dsh-telemetry` | `@deepseek-ai/dsh-sdk-telemetry` | Collides with the `dsh-session-telemetry` family; it is launcher-side SDK telemetry | +| `@deepseek-ai/dsh-helper` | `@deepseek-ai/dsh-sdk-helper` | Indefensibly generic as a published name | +| `@deepseek-ai/dsh-scripts` | `@deepseek-ai/dsh-sdk-scripts` | Same | +| `@deepseek-ai/dsh-timeout-policy` | `@deepseek-ai/dsh-timeout-guard` | Suggestion, not settled: aligns the name with its `guard/` home; decide at resolution time | + +The first four are settled intent; resolving them converges the SDK wire stack's npm names on `dsh-sdk-*` (the npm prefix names the product stack; the `scaffold/` folder names the role). `@deepseek-ai/create-sdk` keeps its documented npm-initializer exception. + +## Implementation + +One mechanical PR, landed in a quiet window: a single conflict event for the many open PRs, an atomic documentation update, and pure-move reviewability (`git mv` + path fixes, rename detection intact). If review prefers, the same content splits into a three-PR stack (the session fold; `ui/` dissolution + `scaffold/`; `self-modification/` + `guard/` merge), at the cost of three rebase events for everyone else. + +A group move touches: the moved package's `tsconfig.json` relative `references` and every dependent's entry (including the `apps/cli` project references), the tsconfig aggregate and path maps, group READMEs (five new bilingual triplets, deletions for dissolved groups, the [packages/README.md](../../../../packages/README.md) hierarchy table, the root `AGENTS.md` layout map), regenerated artifacts (`docs/module-graph.md`, path-embedding catalogs, the lockfile's importer keys), and root-relative `packages/...` citations in prose (root and `apps/cli` READMEs, 7 script files + 22 docs files measured today; `verify-package-paths` gates the Markdown/TypeScript classes of stragglers). This list conveys the shape of the change, not a file inventory: every remaining group-path referent (workspace configs, test globs, lint keys) is found mechanically at implementation time by the acceptance gates failing loud, which is the repository's own misconfiguration rule. + +A group move does not touch: npm names, imports, `cordis.yml` configs, snapshot fixtures, the `pnpm-workspace.yaml`/`tsdown` globs (both `packages/*/*`), or the Python runtime manifest — all reference packages by npm name (verified by grep before this proposal). Each FIXME lands in the same PR as the move that creates its context. + +`client/` and `host/` are out of scope and unchanged. The in-flight `experimental/` group proposal (PR #844) is orthogonal — a release-boundary container, not a clustering decision; the only interaction is a trivial merge in the packages/README.md table. + +## Alternatives considered + +**Coarse domain buckets** (`exec/` = subprocess+sandbox+bash+pty+code-runtime, `workspace/` = fs+lsp+workspace, `orchestration/` = subagent+workflow+tasks, `knowledge/` = web+skill, `collab/` = plan+todo+goal; ~16 groups). Rejected: the measured graph contradicts the merges. `sandbox` and `subprocess` are shared infrastructure consumed across families (bash ×5, fs ×5, pty, lsp, mcp, subagent, sdk edges), `web` ↔ `skill` have zero edges, and a large bucket reproduces the `ui/` grab-bag at a larger scale. + +**Abstract layer names** (`capability/`, `policy/`, `extension/`, `provider/`). Rejected: they describe every plugin equally badly, and a `capability/` bucket would hold ~50 packages. + +**A full npm rename sweep** (`dsh--` for every package). Rejected: npm names are flat, so group-prefixing adds churn across imports, configs, and fixtures with no disambiguation gain; targeted FIXME-tracked renames cover the actual collisions. + +**Performing the five renames inside the reorganization.** Rejected in review: renames multiply open-PR conflicts and destroy the pure-move review property. The FIXME markers keep them visible release blockers to resolve as small follow-up PRs. + +**A two-way session split** (`session-core/` + `session-utils/`). Rejected: query belongs to neither side cleanly, and `session-core` invites confusion with `core/session` (`dsh-session`, the live in-memory service, which stays in `core/`). + +**A three-way session split** (`session-store/` + `session-query/` + `session-utils/`), this RFC's first draft. Rejected in review: `session-utils/` was a negatively-defined annex ("derived, nothing load-bearing depends on it") — the grab-bag shape the north star forbids, and factually wrong besides (`session-query` peer-depends on `dsh-session-title`). The invented compound names also read machine-generated; one plain `session/` group says what a human would say. Query stays standalone in both drafts: it is an independently consumed read surface with its own tool package and backend. + +**Recomposing `ui/` as a single `channels/` group** (tui + jsonrpc + acp + interaction seams + boot). Rejected: the same grab-bag under a new name — those packages serve four planes, `jsonrpc`'s measured cluster is the SDK wire stack, and `acp/` is an automation transport, not a human channel. + +**A standalone one-package `tui/` group**, this RFC's first draft. Rejected in review: `tui` is the interaction plane's primary provider/consumer (peer edges to `commands`, `user-interaction`), and a top-level name spent on one plugin adds a group without adding information; it folds into `interaction/`. + +**Keeping the group name `sdk/`.** Rejected in review: the whole `packages/` tree is the SDK, so an `sdk/` group inside it discriminates nothing — the same disease as `cordis/`. `scaffold/` names the actual role (create, launch, and drive projects from outside). + +**Moving `app-boot` to `apps/`** (review question). Rejected: `apps/` is the assembly tier over the package tier, and `dsh-app-boot` is a library that package-tier code imports (`scaffold/scripts`' launcher peer-depends on it) — placing it in `apps/` would invert the tiers and put a workspace library outside the `packages/*/*` build globs. It stays a package; `boot/` is its role-complete home. + +**Moving `tool-cordis` into `core/`.** Rejected: self-modification is its own product seam, expected to grow; the spine stays minimal. The group was first named `self-evolve/`; review settled on `self-modification/` as the plainer term. + +**Renaming `context/` to `request-context/`.** Rejected: within this tree the group is unambiguous in situ; the churn is unjustified. + +## Acceptance criteria + +- The tree matches the map: the six recomposed groups hold exactly the listed members; the groups `ui/`, `sdk/`, `telemetry/`, `timeout/`, `cordis/`, `session-persistence/`, `session-projection/`, and `session-title/` no longer exist; every other group's contents are unchanged. +- Zero npm renames: the workspace package-name set is identical before and after. +- The five FIXME markers are present, each naming its intended new name. +- New and updated group READMEs form complete bilingual triplets; the packages/README.md hierarchy table matches the tree; docs/module-graph.md is regenerated. +- `pnpm run typecheck`, `pnpm run test`, `pnpm run doc-sync`, and `pnpm run hygiene` pass, and the unit/snapshot suites collect the same test files before and after the move (group-scoped globs in test configs must not silently match nothing). + +## Risks + +- **Open-PR conflicts.** Every open PR touching a moved file must rebase across the move. Mitigated by landing as one PR in a quiet window, publishing the mapping table in the PR body, and the moves being pure renames (rename detection resolves most hunks mechanically). +- **Reference drift.** Docs or scripts citing old paths can go stale; `verify-package-paths` exists precisely for moved-package drift and runs in `doc-sync`. +- **Translation load.** Five new group README triplets plus edits to existing pairs land in the implementation PR. +- **Deferred renames are release blockers by design.** A FIXME that later proves wrong must be removed explicitly with rationale, never silently dropped. +- **Single-package groups remain** (`boot/`, `self-modification/`, and existing ones such as `acp/`). Accepted deliberately: each is role-complete rather than a fragment of a family, and a truthful small group beats a nominal merge. +- **What this gives up:** nothing functional — the change is navigational. Muscle memory and external links to old GitHub paths break, which is acceptable pre-release with no external consumers. diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md new file mode 100644 index 0000000000..c49c5a1daf --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md @@ -0,0 +1,106 @@ +# Agent Note: 按实测聚类重新划分 packages/ 分组 + +Status: proposed + +[English](2026-07-29-package-regrouping.md) | 中文 + +## Problem + +两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.md))自 6 月以来已经漂移:167 个包(package)如今坐落在 40 个组里,若干组边界已经对不上这些包的实际聚类。 + +- `ui/` 混杂了四个互不相关的平面:人类终端通道(`tui`)、SDK 的 JSON-RPC 服务端一半(`jsonrpc`,它对 `dsh-sdk-protocol` 的对等依赖(peer dependency)把它绑在 `sdk/` 这一栈上)、人机交互 seam(`user-interaction`、`user-approval`、`permission`、`tool-ask-user`、`commands`),以及与通道无关的 boot 胶水(`app-boot`)。它自己的 README 只能逐一叙述这堆混杂,说不出一个统一职责。 +- 会话家族被割裂在五个组里——`session-persistence/`、`session-projection/`、`session-query/`、`session-title/` 与 `telemetry/`——而实测依赖边明明把它们连成一体(query → persistence、title → projection、projection → persistence;见 [docs/module-graph.md](../../../../docs/module-graph.md))。 +- 两个组名与不相干的包撞名:`telemetry/`(会话上报)撞上 `sdk/telemetry` = `dsh-telemetry`(启动器侧 SDK telemetry),`timeout/`(一个工具调用守卫)撞上 `util/timeout`(通用 promise 工具)。 +- `cordis/` 拿所有包共同依托的框架给自己的组命名,这个名字因此毫无区分度;组里唯一的包 `tool-cordis` 是运行时自我修改工具集。 +- `sdk/` 的目录命名不一致:`sdk/sdk-client` 和 `sdk/sdk-protocol` 重复了组名,而 `sdk/telemetry`、`sdk/helper`、`sdk/scripts` 没有。 + +这次重新分组的指导准则:**聚类紧密的包同处一组。**聚类以实测为准(对等依赖边与 co-change),而非按主题归类。孤立的 seam 家族可以自成一个小组;要避免的失败形态,是名字概括不出单一职责的大杂烩组。 + +## Proposal + +重组六个组;其余每个组都保持今天的边界与内容不变(依赖分析确认各能力家族——`bash/`、`pty/`、`code-runtime/`、`sandbox/`、`subprocess/`、`fs/`、`lsp/`、`web/`、`skill/` 及其余——本来就划得正确)。这次重组不改任何 npm 包名;整个变更全部由目录树承载。 + +| 组 | 成员(目录名) | 来源 | +|---|---|---| +| `session/` | session-persistence、session-persistence-jsonl、session-persistence-sqlite、session-checkpoint-policy、session-projection、session-projection-cache、session-title、session-title-llm、session-title-first-message-llm、session-title-all-messages-llm、session-telemetry、session-telemetry-otel | `session-persistence/` + `session-projection/` + `session-title/` + `telemetry/` | +| `interaction/` | user-interaction、user-approval、permission、tool-ask-user、commands、tui | `ui/` | +| `boot/` | app-boot | `ui/` | +| `scaffold/` | helper、scripts、create-sdk、protocol、client、server、telemetry | `sdk/` + `ui/jsonrpc` | +| `guard/` | repeat-tool-guard、timeout-policy | `guard/` + `timeout/` | +| `self-modification/` | tool-cordis | `cordis/` | + +- **`session/`** 是持久会话数据平面:持久化 seam 连同其各后端与检查点策略、从该日志折叠(fold)出全量值对外供值的投影、日志兜底的标题,以及 OTel 上报。标题折叠本身就是读取侧的承重构件(`session-query` 对 `dsh-session-title` 声明对等依赖),所以标题属于数据平面,而非某个「派生服务」附属区。用这个朴素的名字是有意为之(评审意见:名字要像人起的);旁边的 `core/session` 包仍是常驻内存的实时服务,本组则是围绕它的持久家族。`session-query/` 保持独立成组:这个读取/工具面自带模型工具和 SQLite FTS 后端,其消费不依赖持久化内部实现。吸收 `telemetry/` 之后,与 `dsh-telemetry` 的组名冲突就此终结。 +- **`interaction/`** 是人机协作平面加上应答它的终端通道:提问/批准 seam、权限预设、面向模型的 `ask_user_question` 工具、人类命令注册表(`plan-mode` 与 `command-goal` 已经把 `commands` 和各交互 seam 放在一起消费),以及 `tui`——这个交互通道是该平面最重的提供方与消费方(对 `commands` 与 `user-interaction` 均有对等依赖边),而一个单包 `tui/` 组会把一个顶层名字花在一个插件上。 +- **`boot/`** 是角色完备的单包组:不归属任何通道也不归属任何组装的共享 bin boot 胶水(被 `apps/cli`、`scaffold/` 的启动器和 `examples/` 各演示 bin 消费)。 +- **`scaffold/`** 是开发者工具家族:项目 helper、启动器、初始化器、连同两端的通信协议(`server` 即今天的 `ui/jsonrpc`),以及启动器侧 telemetry。评审中从 `sdk/` 改名:整个 `packages/` 树本身就是 SDK,树里再放一个叫 `sdk/` 的组等于什么都没说;`scaffold/` 说出了「创建/启动/驱动项目」这一实际角色。目录去掉遗留的 `sdk-` 前缀(`protocol`、`client`、`server`),与 `client/`/`host/` 的角色命名风格一致。 +- **`guard/`** 保留其文档记载的角色(循环卫生守卫),并新纳入强制执行工具调用超时的包;那个与 `util/timeout` 撞名的单包组 `timeout/` 随之解散。 +- **`self-modification/`** 把 `cordis/` 遮蔽掉的角色说了出来:它是 agent(智能体)检查并挂载自身实时运行时中插件所用的工具集,也是未来自我修改类包的落点。 + +40 个组变为 37 个;收益在聚类正确与名实相符,不在数量增减。 + +## Deferred renames (FIXME markers) + +五个 npm 名最终应当改掉,但在这次重组内部改名,会把一个纯移动的 PR(Pull Request)变成大量翻改 import 的 PR。实现改为在每个受影响包的模块 JSDoc 里加一条 `FIXME`,写明意图中的新名字。`FIXME` 会阻塞打 tag 的发布([标记语义](../../../../docs/development.md)),这正是想要的倒逼机制:只有趁还没有外部消费方使用这些包时,这些改名才是零成本的。 + +| 当前 npm 名 | 目标名 | 原因 | +|---|---|---| +| `@deepseek-ai/dsh-jsonrpc` | `@deepseek-ai/dsh-sdk-server` | 名字说的是协议编码而非角色;它是 SDK 协议的服务端一半 | +| `@deepseek-ai/dsh-telemetry` | `@deepseek-ai/dsh-sdk-telemetry` | 与 `dsh-session-telemetry` 家族撞名;它是启动器侧 SDK telemetry | +| `@deepseek-ai/dsh-helper` | `@deepseek-ai/dsh-sdk-helper` | 作为公开发布名空泛得站不住脚 | +| `@deepseek-ai/dsh-scripts` | `@deepseek-ai/dsh-sdk-scripts` | 同上 | +| `@deepseek-ai/dsh-timeout-policy` | `@deepseek-ai/dsh-timeout-guard` | 仅为建议、尚未定案:使名字与其 `guard/` 归属对齐;到解决时再定 | + +前四个是已定的意图;兑现之后,SDK 通信栈的 npm 名随之收敛为 `dsh-sdk-*`(npm 前缀指产品栈,`scaffold/` 目录名指角色)。`@deepseek-ai/create-sdk` 保留其文档记载的 npm 初始化器特例。 + +## Implementation + +整个变更作为一个机械化 PR,在低活动窗口期合入:众多开放中的 PR 只经历一次冲突事件,文档得到一次原子更新,评审享有纯移动的可评审性(`git mv` 加路径修正,重命名检测完好)。若评审偏好拆分,同样的内容也可以拆成三个 PR 的堆叠(会话折叠;`ui/` 解散加 `scaffold/`;`self-modification/` 加 `guard/` 合并),代价是其他所有人要经历三次变基事件。 + +组移动会触及:被移动包的 `tsconfig.json` 相对 `references` 及每个依赖方的对应条目(含 `apps/cli` 的 project references);tsconfig 聚合与路径映射;各组 README(五组新的双语三文件配对、被解散组的 README 删除、[packages/README.md](../../../../packages/README.md) 的层级结构表、根 `AGENTS.md` 的布局图);重新生成的产物(`docs/module-graph.md`、内嵌路径的目录、锁文件的 importer 键);以及散文中以仓库根为基准的 `packages/...` 引用(根 README 与 `apps/cli` README、今天实测 7 个脚本文件加 22 个文档文件;`verify-package-paths` 兜住 Markdown/TypeScript 这两类漏网引用)。这份清单陈述的是变更的形态,不是文件清点:其余每一处组路径引用(workspace 配置、测试 glob、lint 键)都在实现时由验收门禁的响亮失败机械地找出来——这正是本仓库自己的「配置错误必须响亮失败」规则。 + +组移动不触及:npm 包名、import、`cordis.yml` 配置、快照 fixture(测试前置数据)、`pnpm-workspace.yaml` 与 `tsdown` 的 glob(都是 `packages/*/*`),以及 Python 运行时 manifest(元数据清单)——它们全部按 npm 包名引用包(提案之前已用 grep 核实)。每条 FIXME 都与创造其上下文的那次移动落在同一个 PR。 + +`client/` 与 `host/` 不在本次范围内,保持不变。在途的 `experimental/` 组提案(PR #844)与本案正交:它是发布边界容器,不是聚类决策;两者唯一的交集是 packages/README.md 表格里一次很小的合并。 + +## Alternatives considered + +**粗粒度领域桶**(`exec/` = subprocess+sandbox+bash+pty+code-runtime,`workspace/` = fs+lsp+workspace,`orchestration/` = subagent+workflow+tasks,`knowledge/` = web+skill,`collab/` = plan+todo+goal;约 16 个组)。不予采纳:实测依赖图与这些合并相矛盾。`sandbox` 和 `subprocess` 是被各家族跨界消费的共享基础设施(与 bash ×5、fs ×5、pty、lsp、mcp、subagent、sdk 均有依赖边),`web` ↔ `skill` 之间零依赖边,而大桶只会在更大尺度上复现 `ui/` 式大杂烩。 + +**抽象分层名**(`capability/`、`policy/`、`extension/`、`provider/`)。不予采纳:这些名字对每个插件都同样地不达意,而且一个 `capability/` 桶会装下约 50 个包。 + +**一轮全量 npm 重命名**(每个包都改为 `dsh--`)。不予采纳:npm 包名是扁平的,加组前缀只会在 import、配置和 fixture 之间制造改动,却换不来任何消歧收益;用 FIXME 跟踪的定点改名足以覆盖真正的撞名。 + +**在重组内部一并完成那五个改名。** 评审中否决:改名会成倍放大开放 PR 的冲突,并破坏纯移动的评审属性。FIXME 标记让这些改名保持为可见的发布阻塞项,留待以小型后续 PR 逐一解决。 + +**会话两分法**(`session-core/` + `session-utils/`)。不予采纳:query 放哪一侧都不干净,而且 `session-core` 容易与 `core/session` 混淆(后者是 `dsh-session`,常驻内存的实时服务,留在 `core/` 不动)。 + +**会话三分法**(`session-store/` + `session-query/` + `session-utils/`),即本 RFC 的初稿。评审中否决:`session-utils/` 是靠否定条件圈出来的附属区(「派生的、没有承重方依赖」)——正是指导准则禁止的大杂烩形态,而且事实层面也站不住(`session-query` 对 `dsh-session-title` 声明对等依赖)。杜撰的复合名也读起来不像人起的;一个朴素的 `session/` 组说的就是人会说的话。query 在两版草稿中都保持独立:它是被独立消费的读取面,自带自己的工具包与后端。 + +**把 `ui/` 重组为单一 `channels/` 组**(tui + jsonrpc + acp + 交互 seam + boot)。不予采纳:不过是换个名字的同一个大杂烩——这些包服务于四个平面,`jsonrpc` 的实测聚类归属是 SDK 通信栈,而 `acp/` 是自动化传输通道,不是人类通道。 + +**独立的单包 `tui/` 组**,即本 RFC 的初稿。评审中否决:`tui` 是交互平面最重的提供方/消费方(对 `commands`、`user-interaction` 有对等依赖边),把一个顶层名字花在一个插件上只添组不添信息;它折入 `interaction/`。 + +**保留组名 `sdk/`。** 评审中否决:整个 `packages/` 树本身就是 SDK,树里的 `sdk/` 组毫无区分度——与 `cordis/` 同病。`scaffold/` 说出了实际角色(从外部创建、启动、驱动项目)。 + +**把 `app-boot` 挪到 `apps/`**(评审提问)。不予采纳:`apps/` 是包层之上的组装层,而 `dsh-app-boot` 是被包层代码 import 的库(`scaffold/scripts` 的启动器对它声明对等依赖)——放进 `apps/` 会颠倒层级,并把一个 workspace 库放到 `packages/*/*` 构建 glob 之外。它仍是一个包;`boot/` 是它角色完备的家。 + +**把 `tool-cordis` 挪进 `core/`。** 不予采纳:自我修改是独立的产品 seam,预期还会生长;主干保持精简。该组最初命名为 `self-evolve/`;评审定为更朴素的 `self-modification/`。 + +**把 `context/` 改名为 `request-context/`。** 不予采纳:在这棵树里,该组就地看并无歧义;这份改动开销并不值得。 + +## Acceptance criteria + +- 目录树与映射表一致:重组的六个组恰好持有所列成员;`ui/`、`sdk/`、`telemetry/`、`timeout/`、`cordis/`、`session-persistence/`、`session-projection/`、`session-title/` 这些组不复存在;其余每个组的内容不变。 +- npm 改名为零:workspace 的包名集合在前后完全相同。 +- 五条 FIXME 标记全部就位,每条都写明其意图中的新名字。 +- 新增与更新的组 README 构成完整的双语三文件配对;packages/README.md 的层级结构表与目录树一致;docs/module-graph.md 已重新生成。 +- `pnpm run typecheck`、`pnpm run test`、`pnpm run doc-sync`、`pnpm run hygiene` 全部通过,且单元/快照套件在移动前后收集到相同的测试文件(测试配置里按组划定的 glob 不得无声地匹配为空)。 + +## Risks + +- **开放 PR 的冲突。** 每个触碰被移动文件的开放 PR 都必须跨过这次移动做变基。缓解方式:作为单个 PR 在低活动窗口期落地、在 PR 正文公布映射表,且移动都是纯改名(重命名检测可机械化解决大多数改动块)。 +- **引用漂移。** 引用旧路径的文档或脚本可能变得陈旧;`verify-package-paths` 正是为包移动造成的漂移而设,并随 `doc-sync`(文档同步门禁)运行。 +- **翻译负担。** 五组新的组 README 三文件配对,加上既有配对的修改,都随实现 PR 一同落地。 +- **推迟的改名按设计就是发布阻塞项。** 日后若某条 FIXME 被证明不对,必须连同理由显式移除,绝不允许无声消失。 +- **单包组依然存在**(`boot/`、`self-modification/`,以及 `acp/` 等既有单包组)。这是有意接受的:每个都是角色完备的整体而非某个家族的碎片,一个名实相符的小组胜过一次徒有其名的合并。 +- **这次变更放弃了什么:** 功能上一无所失——变更只关乎导航。肌肉记忆和指向旧 GitHub 路径的外部链接会失效;在 pre-release、尚无外部消费者的前提下,这可以接受。 From 7e445c3a676596b373cc20478384352ad86d8299 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 01:52:06 +0800 Subject: [PATCH 02/19] refactor(session): fold the session family into packages/session/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git mv the 12 packages from session-persistence/, session-projection/, session-title/, and telemetry/ into one session/ group per the regrouping RFC; merge the four group READMEs into one bilingual triplet; rewrite the group segment in tsconfig references (intra-group references shorten to ../), tsconfig.base.json paths/globs, knip.json keys, vitest include, gate scripts, and authored doc/note citations; regenerate module graph, doc graphs, catalogs, and the lockfile importer keys. No npm names change. Full unit suite: 8779 passed; the 18 reported failures reproduce as env flakes (ambient-proxy IPv6 tunneling, watched-dir inotify timeouts under parallel load) — each passes in isolation with NO_PROXY set, matching their known pre-existing behavior on master. --- .../2026-06-18-session-surface.i18n.yaml | 4 +- .../2026-06-18-session-surface.md | 6 +-- .../2026-06-18-session-surface.zh.md | 6 +-- ...-07-21-log-backed-session-titles.i18n.yaml | 4 +- .../2026-07-21-log-backed-session-titles.md | 2 +- ...2026-07-21-log-backed-session-titles.zh.md | 2 +- ...3-session-telemetry-otel-revival.i18n.yaml | 4 +- ...26-07-23-session-telemetry-otel-revival.md | 2 +- ...07-23-session-telemetry-otel-revival.zh.md | 2 +- .../2026-07-06-node-engine-floor.i18n.yaml | 4 +- .../process/2026-07-06-node-engine-floor.md | 2 +- .../2026-07-06-node-engine-floor.zh.md | 2 +- ...-domain-kv-storage-and-workspace.i18n.yaml | 4 +- ...6-07-24-domain-kv-storage-and-workspace.md | 4 +- ...7-24-domain-kv-storage-and-workspace.zh.md | 2 +- ...root-and-derived-medium-recovery.i18n.yaml | 4 +- ...torage-root-and-derived-medium-recovery.md | 2 +- ...age-root-and-derived-medium-recovery.zh.md | 2 +- ...ssembled-assistant-messages-only.i18n.yaml | 4 +- ...06-20-assembled-assistant-messages-only.md | 2 +- ...20-assembled-assistant-messages-only.zh.md | 2 +- ...ld-session-persistence-interface.i18n.yaml | 4 +- ...6-20-fold-session-persistence-interface.md | 2 +- ...0-fold-session-persistence-interface.zh.md | 2 +- ...06-20-truncate-interrupted-turns.i18n.yaml | 4 +- .../2026-06-20-truncate-interrupted-turns.md | 2 +- ...026-06-20-truncate-interrupted-turns.zh.md | 2 +- AGENTS.md | 2 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 4 +- docs/architecture.zh.md | 4 +- docs/capability-seams.md | 12 ++--- .../persistence.i18n.yaml | 4 +- docs/core-data-structures/persistence.md | 6 +-- docs/core-data-structures/persistence.zh.md | 6 +-- .../session-title.i18n.yaml | 4 +- docs/core-data-structures/session-title.md | 4 +- docs/core-data-structures/session-title.zh.md | 4 +- docs/persistence-catalog.md | 4 +- knip.json | 6 +-- packages/README.i18n.yaml | 4 +- packages/README.md | 5 +- packages/README.zh.md | 5 +- packages/client/runtime/tsconfig.json | 4 +- packages/client/ui-conversation/tsconfig.json | 2 +- packages/core/agent-loop/tsconfig.json | 2 +- .../examples/acp-demo/tests/built-bin.e2e.ts | 4 +- packages/examples/acp-demo/tsconfig.json | 4 +- .../examples/agent-spine-demo/tsconfig.json | 2 +- packages/goal/goal/tsconfig.json | 2 +- packages/hooks/hooks-claude/tsconfig.json | 2 +- packages/hooks/hooks-codex/tsconfig.json | 2 +- packages/host/apiproxy/tsconfig.json | 8 +-- packages/llm/token-meter/tsconfig.json | 2 +- packages/plan/plan-mode/tsconfig.json | 2 +- packages/sdk/helper/tsconfig.json | 4 +- packages/session-persistence/README.i18n.yaml | 6 --- packages/session-persistence/README.md | 14 ------ packages/session-persistence/README.zh.md | 14 ------ packages/session-projection/README.i18n.yaml | 6 --- packages/session-projection/README.md | 10 ---- packages/session-projection/README.zh.md | 10 ---- .../session-query-sqlite/tsconfig.json | 2 +- .../session-query/session-query/tsconfig.json | 4 +- packages/session-title/README.md | 14 ------ packages/session-title/README.zh.md | 14 ------ .../README.i18n.yaml | 6 +-- packages/session/README.md | 49 +++++++++++++++++++ packages/session/README.zh.md | 49 +++++++++++++++++++ .../README.i18n.yaml | 0 .../session-checkpoint-policy/README.md | 0 .../session-checkpoint-policy/README.zh.md | 0 .../session-checkpoint-policy/package.json | 0 .../session-checkpoint-policy/src/index.ts | 0 .../src/invariant.ts | 0 .../tests/crash-recovery.e2e.ts | 0 .../tests/fixtures/crash-child.ts | 0 .../tests/session-checkpoint-policy.spec.ts | 0 .../session-checkpoint-policy/tsconfig.json | 2 +- .../README.i18n.yaml | 0 .../session-persistence-jsonl/README.md | 0 .../session-persistence-jsonl/README.zh.md | 0 .../session-persistence-jsonl/package.json | 0 .../session-persistence-jsonl/src/format.ts | 0 .../session-persistence-jsonl/src/index.ts | 0 .../src/invariant.ts | 0 .../session-persistence-jsonl/src/win32.ts | 0 .../src/zstd-private-decoder.ts | 0 .../src/zstd-public-decoder.ts | 0 .../session-persistence-jsonl/src/zstd.ts | 0 .../tests/jsonl.spec.ts | 0 .../tests/win32.spec.ts | 0 .../tests/zstd.compat.spec.ts | 0 .../tests/zstd.spec.ts | 0 .../session-persistence-jsonl/tsconfig.json | 2 +- .../README.i18n.yaml | 0 .../session-persistence-sqlite/README.md | 0 .../session-persistence-sqlite/README.zh.md | 0 .../session-persistence-sqlite/package.json | 0 .../session-persistence-sqlite/src/index.ts | 0 .../src/invariant.ts | 0 .../session-persistence-sqlite/src/schema.ts | 0 .../tests/sqlite.spec.ts | 0 .../session-persistence-sqlite/tsconfig.json | 2 +- .../session-persistence/README.i18n.yaml | 0 .../session-persistence/README.md | 0 .../session-persistence/README.zh.md | 0 .../session-persistence/package.json | 0 .../session-persistence/src/coordinator.ts | 0 .../session-persistence/src/index.ts | 0 .../session-persistence/src/invariant.ts | 0 .../session-persistence/src/preparations.ts | 0 .../session-persistence/src/revision.ts | 0 .../session-persistence/src/write-behind.ts | 0 .../session-persistence/tests/contract.ts | 0 .../tests/coordinator-contract.ts | 0 .../tests/persistence.spec.ts | 0 .../tests/preparations.spec.ts | 0 .../tests/write-behind.spec.ts | 0 .../session-persistence/tsconfig.json | 0 .../session-projection-cache/README.i18n.yaml | 0 .../session-projection-cache/README.md | 0 .../session-projection-cache/README.zh.md | 0 .../session-projection-cache/package.json | 0 .../session-projection-cache/src/index.ts | 0 .../session-projection-cache/src/invariant.ts | 0 .../session-projection-cache/src/spec.ts | 0 .../tests/cache.spec.ts | 0 .../session-projection-cache/tsconfig.json | 2 +- .../session-projection/README.i18n.yaml | 0 .../session-projection/README.md | 0 .../session-projection/README.zh.md | 0 .../session-projection/package.json | 0 .../session-projection/src/index.ts | 0 .../session-projection/src/invariant.ts | 0 .../session-projection/src/types.ts | 0 .../session-projection/tests/registry.spec.ts | 0 .../session-projection/tsconfig.json | 0 .../session-telemetry-otel/README.i18n.yaml | 0 .../session-telemetry-otel/README.md | 0 .../session-telemetry-otel/README.zh.md | 0 .../session-telemetry-otel/package.json | 0 .../session-telemetry-otel/src/index.ts | 0 .../session-telemetry-otel/src/invariant.ts | 0 .../session-telemetry-otel/src/user-id.ts | 0 .../tests/loader-composition.e2e.ts | 0 .../session-telemetry-otel/tests/otel.spec.ts | 0 .../tests/user-id.spec.ts | 0 .../session-telemetry-otel/tsconfig.json | 0 .../session-telemetry/README.i18n.yaml | 0 .../session-telemetry/README.md | 0 .../session-telemetry/README.zh.md | 0 .../session-telemetry/package.json | 0 .../session-telemetry/src/coordinator.ts | 0 .../session-telemetry/src/index.ts | 0 .../session-telemetry/src/invariant.ts | 0 .../session-telemetry/tests/redact.spec.ts | 0 .../session-telemetry/tests/telemetry.spec.ts | 0 .../session-telemetry/tsconfig.json | 0 .../README.i18n.yaml | 0 .../session-title-all-messages-llm/README.md | 0 .../README.zh.md | 0 .../package.json | 0 .../src/index.ts | 0 .../src/invariant.ts | 0 .../tests/provider.spec.ts | 0 .../tsconfig.json | 0 .../README.i18n.yaml | 0 .../session-title-first-message-llm/README.md | 0 .../README.zh.md | 0 .../package.json | 0 .../src/index.ts | 0 .../src/invariant.ts | 0 .../tests/loader-composition.spec.ts | 0 .../tests/provider.e2e.ts | 0 .../tests/provider.spec.ts | 0 .../tsconfig.json | 0 .../session-title-llm/README.i18n.yaml | 0 .../session-title-llm/README.md | 0 .../session-title-llm/README.zh.md | 0 .../session-title-llm/package.json | 0 .../session-title-llm/src/index.ts | 0 .../session-title-llm/src/invariant.ts | 0 .../session-title-llm/tests/llm.spec.ts | 0 .../session-title-llm/tsconfig.json | 0 .../session-title/README.i18n.yaml | 0 .../session-title/README.md | 0 .../session-title/README.zh.md | 0 .../session-title/package.json | 0 .../session-title/src/client.ts | 0 .../session-title/src/index.ts | 0 .../session-title/src/invariant.ts | 0 .../session-title/src/normalize.ts | 0 .../session-title/src/types.ts | 0 .../session-title/tests/invariant.spec.ts | 0 .../session-title/tests/persistence.spec.ts | 0 .../session-title/tests/projection.spec.ts | 0 .../session-title/tests/provider.spec.ts | 0 .../session-title/tests/rename.spec.ts | 0 .../tests/service-contracts.spec.ts | 0 .../session-title/tests/session-title.spec.ts | 0 .../session-title/tsconfig.json | 2 +- packages/subagent/subagent/tsconfig.json | 2 +- packages/telemetry/README.i18n.yaml | 6 --- packages/telemetry/README.md | 10 ---- packages/telemetry/README.zh.md | 10 ---- packages/todo/tool-todo/README.i18n.yaml | 4 +- packages/todo/tool-todo/README.md | 2 +- packages/todo/tool-todo/README.zh.md | 2 +- packages/todo/tool-todo/tsconfig.json | 2 +- .../jsonrpc/tests/built-scope-carrier.e2e.ts | 2 +- packages/ui/permission/tsconfig.json | 2 +- packages/workspace/workspace/tsconfig.json | 2 +- scripts/gen-cordis-catalog.ts | 12 ++--- scripts/run-gates.ts | 2 +- scripts/type-equiv.manifest.json | 28 +++++------ .../verify-package-readme-model-experience.ts | 8 +-- tsconfig.base.json | 16 ++---- tsconfig.host.json | 24 ++++----- vitest.config.ts | 2 +- 220 files changed, 258 insertions(+), 286 deletions(-) delete mode 100644 packages/session-persistence/README.i18n.yaml delete mode 100644 packages/session-persistence/README.md delete mode 100644 packages/session-persistence/README.zh.md delete mode 100644 packages/session-projection/README.i18n.yaml delete mode 100644 packages/session-projection/README.md delete mode 100644 packages/session-projection/README.zh.md delete mode 100644 packages/session-title/README.md delete mode 100644 packages/session-title/README.zh.md rename packages/{session-title => session}/README.i18n.yaml (71%) create mode 100644 packages/session/README.md create mode 100644 packages/session/README.zh.md rename packages/{session-persistence => session}/session-checkpoint-policy/README.i18n.yaml (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/README.md (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/README.zh.md (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/package.json (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/src/index.ts (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/src/invariant.ts (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/tests/crash-recovery.e2e.ts (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/tests/fixtures/crash-child.ts (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts (100%) rename packages/{session-persistence => session}/session-checkpoint-policy/tsconfig.json (89%) rename packages/{session-persistence => session}/session-persistence-jsonl/README.i18n.yaml (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/README.md (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/README.zh.md (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/package.json (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/format.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/index.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/invariant.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/win32.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/zstd-private-decoder.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/zstd-public-decoder.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/src/zstd.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/tests/jsonl.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/tests/win32.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/tests/zstd.compat.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/tests/zstd.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence-jsonl/tsconfig.json (87%) rename packages/{session-persistence => session}/session-persistence-sqlite/README.i18n.yaml (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/README.md (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/README.zh.md (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/package.json (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/src/index.ts (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/src/invariant.ts (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/src/schema.ts (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/tests/sqlite.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence-sqlite/tsconfig.json (87%) rename packages/{session-persistence => session}/session-persistence/README.i18n.yaml (100%) rename packages/{session-persistence => session}/session-persistence/README.md (100%) rename packages/{session-persistence => session}/session-persistence/README.zh.md (100%) rename packages/{session-persistence => session}/session-persistence/package.json (100%) rename packages/{session-persistence => session}/session-persistence/src/coordinator.ts (100%) rename packages/{session-persistence => session}/session-persistence/src/index.ts (100%) rename packages/{session-persistence => session}/session-persistence/src/invariant.ts (100%) rename packages/{session-persistence => session}/session-persistence/src/preparations.ts (100%) rename packages/{session-persistence => session}/session-persistence/src/revision.ts (100%) rename packages/{session-persistence => session}/session-persistence/src/write-behind.ts (100%) rename packages/{session-persistence => session}/session-persistence/tests/contract.ts (100%) rename packages/{session-persistence => session}/session-persistence/tests/coordinator-contract.ts (100%) rename packages/{session-persistence => session}/session-persistence/tests/persistence.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence/tests/preparations.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence/tests/write-behind.spec.ts (100%) rename packages/{session-persistence => session}/session-persistence/tsconfig.json (100%) rename packages/{session-projection => session}/session-projection-cache/README.i18n.yaml (100%) rename packages/{session-projection => session}/session-projection-cache/README.md (100%) rename packages/{session-projection => session}/session-projection-cache/README.zh.md (100%) rename packages/{session-projection => session}/session-projection-cache/package.json (100%) rename packages/{session-projection => session}/session-projection-cache/src/index.ts (100%) rename packages/{session-projection => session}/session-projection-cache/src/invariant.ts (100%) rename packages/{session-projection => session}/session-projection-cache/src/spec.ts (100%) rename packages/{session-projection => session}/session-projection-cache/tests/cache.spec.ts (100%) rename packages/{session-projection => session}/session-projection-cache/tsconfig.json (90%) rename packages/{session-projection => session}/session-projection/README.i18n.yaml (100%) rename packages/{session-projection => session}/session-projection/README.md (100%) rename packages/{session-projection => session}/session-projection/README.zh.md (100%) rename packages/{session-projection => session}/session-projection/package.json (100%) rename packages/{session-projection => session}/session-projection/src/index.ts (100%) rename packages/{session-projection => session}/session-projection/src/invariant.ts (100%) rename packages/{session-projection => session}/session-projection/src/types.ts (100%) rename packages/{session-projection => session}/session-projection/tests/registry.spec.ts (100%) rename packages/{session-projection => session}/session-projection/tsconfig.json (100%) rename packages/{telemetry => session}/session-telemetry-otel/README.i18n.yaml (100%) rename packages/{telemetry => session}/session-telemetry-otel/README.md (100%) rename packages/{telemetry => session}/session-telemetry-otel/README.zh.md (100%) rename packages/{telemetry => session}/session-telemetry-otel/package.json (100%) rename packages/{telemetry => session}/session-telemetry-otel/src/index.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/src/invariant.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/src/user-id.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/tests/loader-composition.e2e.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/tests/otel.spec.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/tests/user-id.spec.ts (100%) rename packages/{telemetry => session}/session-telemetry-otel/tsconfig.json (100%) rename packages/{telemetry => session}/session-telemetry/README.i18n.yaml (100%) rename packages/{telemetry => session}/session-telemetry/README.md (100%) rename packages/{telemetry => session}/session-telemetry/README.zh.md (100%) rename packages/{telemetry => session}/session-telemetry/package.json (100%) rename packages/{telemetry => session}/session-telemetry/src/coordinator.ts (100%) rename packages/{telemetry => session}/session-telemetry/src/index.ts (100%) rename packages/{telemetry => session}/session-telemetry/src/invariant.ts (100%) rename packages/{telemetry => session}/session-telemetry/tests/redact.spec.ts (100%) rename packages/{telemetry => session}/session-telemetry/tests/telemetry.spec.ts (100%) rename packages/{telemetry => session}/session-telemetry/tsconfig.json (100%) rename packages/{session-title => session}/session-title-all-messages-llm/README.i18n.yaml (100%) rename packages/{session-title => session}/session-title-all-messages-llm/README.md (100%) rename packages/{session-title => session}/session-title-all-messages-llm/README.zh.md (100%) rename packages/{session-title => session}/session-title-all-messages-llm/package.json (100%) rename packages/{session-title => session}/session-title-all-messages-llm/src/index.ts (100%) rename packages/{session-title => session}/session-title-all-messages-llm/src/invariant.ts (100%) rename packages/{session-title => session}/session-title-all-messages-llm/tests/provider.spec.ts (100%) rename packages/{session-title => session}/session-title-all-messages-llm/tsconfig.json (100%) rename packages/{session-title => session}/session-title-first-message-llm/README.i18n.yaml (100%) rename packages/{session-title => session}/session-title-first-message-llm/README.md (100%) rename packages/{session-title => session}/session-title-first-message-llm/README.zh.md (100%) rename packages/{session-title => session}/session-title-first-message-llm/package.json (100%) rename packages/{session-title => session}/session-title-first-message-llm/src/index.ts (100%) rename packages/{session-title => session}/session-title-first-message-llm/src/invariant.ts (100%) rename packages/{session-title => session}/session-title-first-message-llm/tests/loader-composition.spec.ts (100%) rename packages/{session-title => session}/session-title-first-message-llm/tests/provider.e2e.ts (100%) rename packages/{session-title => session}/session-title-first-message-llm/tests/provider.spec.ts (100%) rename packages/{session-title => session}/session-title-first-message-llm/tsconfig.json (100%) rename packages/{session-title => session}/session-title-llm/README.i18n.yaml (100%) rename packages/{session-title => session}/session-title-llm/README.md (100%) rename packages/{session-title => session}/session-title-llm/README.zh.md (100%) rename packages/{session-title => session}/session-title-llm/package.json (100%) rename packages/{session-title => session}/session-title-llm/src/index.ts (100%) rename packages/{session-title => session}/session-title-llm/src/invariant.ts (100%) rename packages/{session-title => session}/session-title-llm/tests/llm.spec.ts (100%) rename packages/{session-title => session}/session-title-llm/tsconfig.json (100%) rename packages/{session-title => session}/session-title/README.i18n.yaml (100%) rename packages/{session-title => session}/session-title/README.md (100%) rename packages/{session-title => session}/session-title/README.zh.md (100%) rename packages/{session-title => session}/session-title/package.json (100%) rename packages/{session-title => session}/session-title/src/client.ts (100%) rename packages/{session-title => session}/session-title/src/index.ts (100%) rename packages/{session-title => session}/session-title/src/invariant.ts (100%) rename packages/{session-title => session}/session-title/src/normalize.ts (100%) rename packages/{session-title => session}/session-title/src/types.ts (100%) rename packages/{session-title => session}/session-title/tests/invariant.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/persistence.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/projection.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/provider.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/rename.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/service-contracts.spec.ts (100%) rename packages/{session-title => session}/session-title/tests/session-title.spec.ts (100%) rename packages/{session-title => session}/session-title/tsconfig.json (90%) delete mode 100644 packages/telemetry/README.i18n.yaml delete mode 100644 packages/telemetry/README.md delete mode 100644 packages/telemetry/README.zh.md diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml index 7c05a0ada8..911d0709f1 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-18-session-surface.md -2026-06-18-session-surface.md: eeac53534c70099e4102aff9ef226702ea939654 -2026-06-18-session-surface.zh.md: c58d3da049cd6c18e564e596354f5d1831c4756f +2026-06-18-session-surface.md: 1bb3baac5e9bea3f657cbd3be2623223bda78ce2 +2026-06-18-session-surface.zh.md: bfbfd097ffeca59dec163dff911315d0ed94d028 diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md index eeac53534c..1bb3baac5e 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.md @@ -64,9 +64,9 @@ Every surface-eligible event must carry `surfaceOp` or it would disappear from d - **`packages/core/session`**: `surface.ts` (`SurfaceManager`) maintains one ordered seq array for candidate acceptance and live projection; `SessionSurface` is its readonly public view. `SurfaceOp`/`SurfaceIntent` and the top-level session-event fields record how entries join it. `append()` requires a `SurfaceIntent` for surface events, `deriveMessages()` walks the surface as the sole derivation path, and `repair.ts` emits surface-aware closers. The seed constructor rejects a surface-eligible seed event missing its `surfaceOp` marker (see § Invariants). - **`packages/core/agent-loop`**: All surface-capable appends pass surface opts. Chunk seqs are collected for `assistant/message` provenance; `tool/call` seqs are captured for `tool/result` provenance. -- **`packages/session-persistence/session-persistence-sqlite`**: Two new nullable TEXT columns (`source_event_seqs`, `surface_op`) on the `events` table; `SCHEMA_VERSION` bumped (bump-and-reject, no migration). -- **`packages/session-persistence/session-persistence-jsonl`**: No changes required. -- **`packages/session-persistence/session-persistence`**: Abstract interface unchanged. +- **`packages/session/session-persistence-sqlite`**: Two new nullable TEXT columns (`source_event_seqs`, `surface_op`) on the `events` table; `SCHEMA_VERSION` bumped (bump-and-reject, no migration). +- **`packages/session/session-persistence-jsonl`**: No changes required. +- **`packages/session/session-persistence`**: Abstract interface unchanged. The surface is the foundation for future history manipulation. A compaction or tool-result-prune plugin appends one of the existing message-producing event types (a `user/message` carrying the summary, say) with `surfaceOp: { op: 'replace', start, end }` and `sourceEventSeqs` covering the shadowed entries — the new event takes the range's place on the surface while the plugin's own trace events (e.g. `compaction/start`, `compaction/end`) stay off it. Replay preserves the decision deterministically. diff --git a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md index c58d3da049..bfbfd097ff 100644 --- a/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-18-session-surface.zh.md @@ -64,9 +64,9 @@ export type SurfaceOp = - **`packages/core/session`**:`surface.ts`(`SurfaceManager`)维护一个用于候选接纳和实时投影的有序 seq 数组;`SessionSurface` 是其只读公共视图。`SurfaceOp`/`SurfaceIntent` 与顶层会话事件字段记录条目如何加入它。`append()` 要求 surface 事件携带 `SurfaceIntent`,`deriveMessages()` 以遍历 surface 作为唯一派生路径,`repair.ts` 则发出 surface 感知的闭合事件。种子构造函数拒绝缺少 `surfaceOp` 标记的可进入 surface 的种子事件(见「不变式」一节)。 - **`packages/core/agent-loop`**:所有涉及 surface 事件的追加操作都传入 surface 选项。收集分片 seq 用于 `assistant/message` 溯源;捕获 `tool/call` seq 用于 `tool/result` 溯源。 -- **`packages/session-persistence/session-persistence-sqlite`**:`events` 表新增两个可空 TEXT 列(`source_event_seqs`、`surface_op`);`SCHEMA_VERSION` 递增(bump-and-reject,无迁移)。 -- **`packages/session-persistence/session-persistence-jsonl`**:无需改动。 -- **`packages/session-persistence/session-persistence`**:抽象接口不变。 +- **`packages/session/session-persistence-sqlite`**:`events` 表新增两个可空 TEXT 列(`source_event_seqs`、`surface_op`);`SCHEMA_VERSION` 递增(bump-and-reject,无迁移)。 +- **`packages/session/session-persistence-jsonl`**:无需改动。 +- **`packages/session/session-persistence`**:抽象接口不变。 Surface 是未来历史操纵的基础。压缩或 tool-result-prune 插件追加一个既有的消息产出事件类型(例如一条携带摘要的 `user/message`),附带 `surfaceOp: { op: 'replace', start, end }` 和覆盖被遮蔽条目的 `sourceEventSeqs`——新事件在 surface 上取代该范围的位置,而插件自身的 trace 事件(如 `compaction/start`、`compaction/end`)不进入 surface。回放以确定性方式保留该决策。 diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml index 5c5054b0c9..e109560a2f 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md -2026-07-21-log-backed-session-titles.md: 5f4c53a5fb866663bbfaae6b3fdafb4660424804 -2026-07-21-log-backed-session-titles.zh.md: f9629c1109c8428c74270de9475423d29b99e8f1 +2026-07-21-log-backed-session-titles.md: 89e6e67fb9ece4c087ba7b1bc52e61b78b94586c +2026-07-21-log-backed-session-titles.zh.md: 4df21be565f57db056f506df477f665908d1fdfa diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md index 5f4c53a5fb..89e6e67fb9 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md @@ -12,7 +12,7 @@ Session identity metadata is immutable, and the event log is the replay and fork ## Decision -The [`session-title` capability family](../../../../packages/session-title/README.md) owns title state and generation policy. `@deepseek-ai/dsh-session-title` provides `ctx.sessionTitle`, a deterministic first-message fallback, and a registry for at most one optional asynchronous provider. `@deepseek-ai/dsh-session-title-llm` owns the common auxiliary-model request policy; separate first-message and all-user-messages plugins choose input cadence. The shared agent spine mounts only the fallback service. The Web host mounts that service plus the first-message model provider with explicit overridable limits, so a fresh Web session gains an immediate fallback and then a non-blocking model summary. Other compositions choose either model provider explicitly. +The [`session-title` capability family](../../../../packages/session/README.md) owns title state and generation policy. `@deepseek-ai/dsh-session-title` provides `ctx.sessionTitle`, a deterministic first-message fallback, and a registry for at most one optional asynchronous provider. `@deepseek-ai/dsh-session-title-llm` owns the common auxiliary-model request policy; separate first-message and all-user-messages plugins choose input cadence. The shared agent spine mounts only the fallback service. The Web host mounts that service plus the first-message model provider with explicit overridable limits, so a fresh Web session gains an immediate fallback and then a non-blocking model summary. Other compositions choose either model provider explicitly. ### Event ownership and folding diff --git a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md index f9629c1109..4df21be565 100644 --- a/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md +++ b/.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -[`session-title` 功能包族](../../../../packages/session-title/README.md)负责标题状态和生成策略。`@deepseek-ai/dsh-session-title` 提供 `ctx.sessionTitle`、确定性的首消息回退方案,以及一个至多接受单个可选异步提供方的注册表。`@deepseek-ai/dsh-session-title-llm` 负责通用的辅助模型请求策略;首消息插件和全部用户消息插件分别选择输入调度方式。共享 agent 主干只挂载回退服务。Web host 会挂载该服务和首消息模型提供方,并显式设置可覆盖的限制,因此新建的 Web 会话会立即获得回退标题,随后在不阻塞主响应的情况下获得模型摘要。其他组合需显式选择任一模型提供方。 +[`session-title` 功能包族](../../../../packages/session/README.md)负责标题状态和生成策略。`@deepseek-ai/dsh-session-title` 提供 `ctx.sessionTitle`、确定性的首消息回退方案,以及一个至多接受单个可选异步提供方的注册表。`@deepseek-ai/dsh-session-title-llm` 负责通用的辅助模型请求策略;首消息插件和全部用户消息插件分别选择输入调度方式。共享 agent 主干只挂载回退服务。Web host 会挂载该服务和首消息模型提供方,并显式设置可覆盖的限制,因此新建的 Web 会话会立即获得回退标题,随后在不阻塞主响应的情况下获得模型摘要。其他组合需显式选择任一模型提供方。 ### 事件归属与折叠 diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml index 27786e85a2..58b5371ba2 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md -2026-07-23-session-telemetry-otel-revival.md: f83128e8bf62e0718e59912c16c4e449855aaa1a -2026-07-23-session-telemetry-otel-revival.zh.md: bef3bd9cbc22f363b92880864651dcaa782fc1de +2026-07-23-session-telemetry-otel-revival.md: e6a213af65451a2ae26a5eb685c1a0f6ab220869 +2026-07-23-session-telemetry-otel-revival.zh.md: 4e834f8b129e2062064cf3497370fecf6f88db29 diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md index f83128e8bf..e6a213af65 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md @@ -10,7 +10,7 @@ Every deployment that wants harness sessions in an observability stack must hand ## Decision -`packages/telemetry/` revives the two reviewed packages under the SDK stance — the harness provides the capability, the deployment configures where records go and owns what leaves in them: +`packages/session/` (formerly `telemetry/`) revives the two reviewed packages under the SDK stance — the harness provides the capability, the deployment configures where records go and owns what leaves in them: - **`@deepseek-ai/dsh-session-telemetry`** — the seam. `TelemetryBackend` (`emit`/`flush?`/`shutdown`), the service-registered `Telemetry` form, and `TelemetryCoordinator` owning capture: live adoption with cursor read-back and the per-append firehose (project → `structuredClone` → redact → `emit`, zero I/O), buffer-free on-demand replay from the canonical log, the fixed first-chunk-per-(turn, step) projection, the live `agent/error` relay, and live dispose-time `shutdown` records. - **The `telemetry/record` waterfall** — the delta over the branch version and the seam's redaction extension point. Every record passes it before reaching any backend; the seam ships NO rules of its own — the innermost `next()` is a pass-through, deployments mount their rules as listeners (stacking by transforming `next()`'s return value), and a throwing rule withholds the record fail-closed. Redaction applies to the exported copy only; the canonical log is never rewritten. diff --git a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md index bef3bd9cbc..4e834f8b12 100644 --- a/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md +++ b/.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.zh.md @@ -10,7 +10,7 @@ Status: implemented ## 决策 -`packages/telemetry/` 以 SDK 立场复活这两个经过评审的包——harness 提供能力,部署方配置上报去向并对导出内容负责: +`packages/session/`(原 `telemetry/`)以 SDK 立场复活这两个经过评审的包——harness 提供能力,部署方配置上报去向并对导出内容负责: - **`@deepseek-ai/dsh-session-telemetry`** —— seam 本体。`TelemetryBackend`(`emit`/`flush?`/`shutdown`)、服务注册形态的 `Telemetry`、以及拥有捕获侧的 `TelemetryCoordinator`:带游标回读的实时收养与逐 append 的 firehose(投影 → `structuredClone` → 脱敏 → `emit`,零 I/O)、从权威日志进行的无缓冲按需回放、固定的每个(轮次、步骤)组合首分片投影、实时 `agent/error` 转发,以及实时 dispose(资源释放)时的 `shutdown` 记录。 - **`telemetry/record` waterfall(瀑布式事件)** —— 相对分支版本的增量,也是该 seam 的脱敏扩展点。每条记录抵达任何后端前必经此处;seam 自身不带任何规则——最内层 `next()` 原样透传,部署方以监听器挂载自己的规则(通过变换 `next()` 的返回值堆叠),抛异常的规则将该记录 fail-closed 扣下。脱敏只作用于导出副本;canonical log 永不改写。 diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml index d96a3b4d6c..ddca590dea 100644 --- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml +++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-06-node-engine-floor.md -2026-07-06-node-engine-floor.md: e42b809f83fe388526ecbd74192989e0bcdbba67 -2026-07-06-node-engine-floor.zh.md: 92fa00f7a270f4506cba6c899e9edf6a2fdd3209 +2026-07-06-node-engine-floor.md: ad21fbfe034cbf4bb7b75aaac4113f3139a1832d +2026-07-06-node-engine-floor.zh.md: 0d1c31ed74565f9b20116cd0e666246baad9bf79 diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md index e42b809f83..ad21fbfe03 100644 --- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md +++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md @@ -14,7 +14,7 @@ Set `engines.node` to `^22.19.0 || >=24.0.0` and test keyless CI on `['22.19', 2 Two Node features gate the source runtime: -- **`node:sqlite`** — `packages/session-persistence/session-persistence-sqlite` does a top-level `import { DatabaseSync } from 'node:sqlite'`. The module dropped its `--experimental-sqlite` flag requirement at **22.13** (LTS) and **23.4** (Current); before those, importing it throws at load. +- **`node:sqlite`** — `packages/session/session-persistence-sqlite` does a top-level `import { DatabaseSync } from 'node:sqlite'`. The module dropped its `--experimental-sqlite` flag requirement at **22.13** (LTS) and **23.4** (Current); before those, importing it throws at load. - **Native TypeScript type-stripping** — the built-mode `examples/headless-agent/tests/keyless-smoke.e2e.ts` smoke boots its unexported `.ts` driver under plain `node` (no tsx) and loads the example's `.ts` test adapter (`cli-mock-llm.ts`). Type-stripping is the default from **22.18** (LTS) and **23.6** (Current); before those it needs `--experimental-strip-types`. Those source features clear on the 22.x line at **22.18**, but the installed Pi adapter dependency raises the advertised LTS floor. `@deepseek-ai/dsh-llm-pi-ai` depends on `@earendil-works/pi-ai@0.79.3`, whose package declares `engines.node >=22.19.0`, so the LTS floor is **22.19**. The 24.x branch remains `>=24.0.0`. The disjoint range excludes Node 23 entirely: Node 23.0–23.5 still has at least one flagged source feature, and the 23 line is non-LTS/EOL, so advertising `>=23.6` would add a dead release line and a CI leg no deployment should use. diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md index 92fa00f7a2..0d1c31ed74 100644 --- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md +++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md @@ -14,7 +14,7 @@ Status: implemented 两个 Node 特性决定了源码运行时的门槛: -- **`node:sqlite`**:`packages/session-persistence/session-persistence-sqlite` 在顶层执行 `import { DatabaseSync } from 'node:sqlite'`。该模块在 **22.13**(LTS)和 **23.4**(Current)取消了 `--experimental-sqlite` 标志要求;在此之前,导入它会在加载时抛出异常。 +- **`node:sqlite`**:`packages/session/session-persistence-sqlite` 在顶层执行 `import { DatabaseSync } from 'node:sqlite'`。该模块在 **22.13**(LTS)和 **23.4**(Current)取消了 `--experimental-sqlite` 标志要求;在此之前,导入它会在加载时抛出异常。 - **原生 TypeScript 类型剥离**——构建模式的 `examples/headless-agent/tests/keyless-smoke.e2e.ts` 冒烟测试使用纯 `node`(无 tsx)启动该示例未导出的 `.ts` driver,并加载示例的 `.ts` 测试适配器(`cli-mock-llm.ts`)。类型剥离从 **22.18**(LTS)和 **23.6**(Current)起成为默认行为;更早版本需要 `--experimental-strip-types`。 这些源码特性在 22.x 线上于 **22.18** 全部就绪,但已安装的 Pi 适配器依赖将宣传的 LTS 下限进一步提高。`@deepseek-ai/dsh-llm-pi-ai` 依赖 `@earendil-works/pi-ai@0.79.3`,后者的包声明 `engines.node >=22.19.0`,因此 LTS 下限为 **22.19**。24.x 分支保持 `>=24.0.0`。该不相交范围完全排除了 Node 23:Node 23.0–23.5 至少还有一个源码特性需要标志,而 23 线是非 LTS/已 EOL 的,宣传 `>=23.6` 会增加一条已终止的发布线和一条 CI 分支,而没有任何部署应当使用它。 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml index 176097ad5e..620fa2291f 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md -2026-07-24-domain-kv-storage-and-workspace.md: 230877628428dc88dbddeecfe5f4353cf15e151d -2026-07-24-domain-kv-storage-and-workspace.zh.md: cb1fb1346a28b0c3249848705ca209f57cab766e +2026-07-24-domain-kv-storage-and-workspace.md: 1cb2bc4b29c196dbb33292547c75d01a37f06772 +2026-07-24-domain-kv-storage-and-workspace.zh.md: 15460e305d2d9aca3009025cf0218eb6c54c7237 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md index 2308776284..1cb2bc4b29 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md @@ -6,7 +6,7 @@ English | [中文](2026-07-24-domain-kv-storage-and-workspace.zh.md) ## Problem -The host's only persistence surface is the session event log (`packages/session-persistence`: append-only, one file per session). Anything that does not belong to a single session has nowhere to live, and two real needs exist today: +The host's only persistence surface is the session event log (`packages/session/session-persistence`: append-only, one file per session). Anything that does not belong to a single session has nowhere to live, and two real needs exist today: - **The workspace entity.** The GUI needs workspace as a real object: path, title, and the list of owned sessions. Ownership belongs to the workspace — "which sessions belong to this workspace" is not any single session's fact, so writing it into the session log is semantically wrong. Until now workspace was only a sidebar visual grouping derived from cwd, with no entity (that conclusion has been overturned). - **Dynamic session metadata** (the foreseeable second consumer). Cold session listings read only the first log line (an immutable creation-time snapshot); title, terminal status, and anything that evolves with the session is unavailable. The fix direction is a sidecar metadata table — exactly a KV table with high-frequency per-key updates. @@ -26,7 +26,7 @@ Create the `packages/storage/` group — the `ctx.storage` hub (backend registry | `@deepseek-ai/dsh-storage-sqlite` | `packages/storage/storage-sqlite/` | registers backend `sqlite` | ✓ | | `@deepseek-ai/dsh-storage-domain` | `packages/storage/storage-domain/` | mounts `ctx.storage.domain` | ✓ | | `@deepseek-ai/dsh-workspace` | `packages/workspace/workspace/` | `ctx.workspace` | ✓ | -| `SessionPersistence.delete` extension + cascade orchestration | `packages/session-persistence/*` | new method on the existing seam | ✗ future work (session side untouched this phase) | +| `SessionPersistence.delete` extension + cascade orchestration | `packages/session/session-persistence*` | new method on the existing seam | ✗ future work (session side untouched this phase) | | `workspace.*` / `session.delete` RPC, GUI wiring, boot assembly | — | — | ✗ next phase | (workspace lives in its own group rather than `packages/host/`: the host group's naming rule requires the `dsh-host-*` prefix while this package is named `dsh-workspace`; and the workspace entity is a domain concept, not bound to the host assembly tier. Unrelated to the existing `workspace-context` package — that is an AGENTS.md instruction loader.) diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md index cb1fb1346a..15460e305d 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md @@ -26,7 +26,7 @@ host 侧唯一的持久化面是 session 事件日志(`packages/session-persis | `@deepseek-ai/dsh-storage-sqlite` | `packages/storage/storage-sqlite/` | 注册后端 `sqlite` | ✓ | | `@deepseek-ai/dsh-storage-domain` | `packages/storage/storage-domain/` | 挂载 `ctx.storage.domain` | ✓ | | `@deepseek-ai/dsh-workspace` | `packages/workspace/workspace/` | `ctx.workspace` | ✓ | -| `SessionPersistence.delete` 扩面 + 级联删编排 | `packages/session-persistence/*` | 既有 seam 新方法 | ✗ future work(本期不动 session 侧) | +| `SessionPersistence.delete` 扩面 + 级联删编排 | `packages/session/*` | 既有 seam 新方法 | ✗ future work(本期不动 session 侧) | | `workspace.*` / `session.delete` RPC、GUI 接线、boot 组装 | — | — | ✗ 下期 | (workspace 放独立组不放 `packages/host/`:host 组命名规则要求 `dsh-host-*` 前缀,而包名定为 `dsh-workspace`;且 workspace 实体是领域概念,不绑定 host 装配层。与既有 `workspace-context` 包无关——那是 AGENTS.md 指令加载器。) diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml index f1bd359e19..040d099f38 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md -2026-07-28-storage-root-and-derived-medium-recovery.md: 2edb60d204da736bf14ace6278fa4ee7b9e4b8f7 -2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 802ca16f399446777203581d246444e53d632a34 +2026-07-28-storage-root-and-derived-medium-recovery.md: c7b423770fcca89c54d3866db7701348351cc259 +2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 54a2bbc6496bd8cce594b3ce3a105e4be6e8ff64 diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md index 2edb60d204..c7b423770f 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md @@ -8,7 +8,7 @@ English | [中文](2026-07-28-storage-root-and-derived-medium-recovery.zh.md) The persisted projection cache ([RFC](2026-07-27-session-projection-and-command-log.md), shipped as `dsh-session-projection-cache`) surfaced two gaps in the storage substrate it landed on. Both are properties of the domain-KV stack ([design](2026-07-24-domain-kv-storage-and-workspace.md)), not of the cache itself, and both bite the cache first because it is the first *derived* medium on that stack. -**Where the files actually live (root mismatch closed; resolve-once residual still open).** The shared base defaults the session store to the global harness home (`$DSH_HOME/sessions`, default `~/.dsh/sessions`), while the shipped Web overlay used to give the json backend the relative root `./.storages`: `workspace.json` and `session_projcache.json` landed under `/.storages/` — two launches from different directories shared their sessions yet saw different workspace registries and different projection caches, and the cache exists precisely to serve the cross-session cold listing, which missed for every session last cached under another launch directory. That mismatch is now closed: the overlay anchors `storage-json.root` to `$DSH_HOME/storages` with the same `!!js` expression the session root uses (`apps/cli/config/web.cordis.yml`). The residual hazard: `JsonStorageBackend` still never resolves its root — each unit open joins the path against whatever `process.cwd()` is at that moment (packages/storage/storage-json/src/index.ts); the shipped overlay root is already absolute and unaffected, but any relative root (bare Loader boots, tests) still splits on a later cwd change — the exact hazard the JSONL session backend resolves-once to prevent ("later process.cwd() changes cannot split one backend across roots", packages/session-persistence/session-persistence-jsonl/src/index.ts). +**Where the files actually live (root mismatch closed; resolve-once residual still open).** The shared base defaults the session store to the global harness home (`$DSH_HOME/sessions`, default `~/.dsh/sessions`), while the shipped Web overlay used to give the json backend the relative root `./.storages`: `workspace.json` and `session_projcache.json` landed under `/.storages/` — two launches from different directories shared their sessions yet saw different workspace registries and different projection caches, and the cache exists precisely to serve the cross-session cold listing, which missed for every session last cached under another launch directory. That mismatch is now closed: the overlay anchors `storage-json.root` to `$DSH_HOME/storages` with the same `!!js` expression the session root uses (`apps/cli/config/web.cordis.yml`). The residual hazard: `JsonStorageBackend` still never resolves its root — each unit open joins the path against whatever `process.cwd()` is at that moment (packages/storage/storage-json/src/index.ts); the shipped overlay root is already absolute and unaffected, but any relative root (bare Loader boots, tests) still splits on a later cwd change — the exact hazard the JSONL session backend resolves-once to prevent ("later process.cwd() changes cannot split one backend across roots", packages/session/session-persistence-jsonl/src/index.ts). **How recovery works today.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which today describes an aspiration, not the implementation. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change. diff --git a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md index 802ca16f39..54a2bbc649 100644 --- a/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.zh.md @@ -8,7 +8,7 @@ Status: proposed 持久投影缓存([RFC](2026-07-27-session-projection-and-command-log.md),已作为 `dsh-session-projection-cache` 落地)暴露了它所依托的存储基座的两个缺口。二者都是 domain-KV 栈([设计](2026-07-24-domain-kv-storage-and-workspace.md))的属性而非缓存自身的问题,且都首先咬到缓存——因为它是这条栈上第一个*派生*介质。 -**文件到底存在哪(根错位已收口,resolve-once 残余仍开放)。** 共享 base 将会话存储默认为全局 harness home(`$DSH_HOME/sessions`,默认 `~/.dsh/sessions`),而出厂 Web overlay 曾给 json 后端相对根 `./.storages`:`workspace.json` 和 `session_projcache.json` 落在 `<启动目录>/.storages/` 下——从两个不同目录启动,会话相同,工作区注册表和投影缓存却各是一份,而缓存存在的意义恰恰是跨会话冷列表,凡上次在别的启动目录下缓存过的会话全部 miss。这一错位已消除:overlay 现以与会话根同一段 `!!js` 表达式把 `storage-json.root` 锚定到 `$DSH_HOME/storages`(`apps/cli/config/web.cordis.yml`)。残余隐患:`JsonStorageBackend` 仍从不 resolve 根——每次打开 unit 都把路径 join 到当时的 `process.cwd()` 上(packages/storage/storage-json/src/index.ts);出厂 overlay 的根已是绝对路径不受影响,但任何相对根(裸 Loader 启动、测试)仍会被后续 cwd 变化劈开,JSONL 会话后端用「构造时 resolve 一次」防住的正是它("later process.cwd() changes cannot split one backend across roots",packages/session-persistence/session-persistence-jsonl/src/index.ts)。 +**文件到底存在哪(根错位已收口,resolve-once 残余仍开放)。** 共享 base 将会话存储默认为全局 harness home(`$DSH_HOME/sessions`,默认 `~/.dsh/sessions`),而出厂 Web overlay 曾给 json 后端相对根 `./.storages`:`workspace.json` 和 `session_projcache.json` 落在 `<启动目录>/.storages/` 下——从两个不同目录启动,会话相同,工作区注册表和投影缓存却各是一份,而缓存存在的意义恰恰是跨会话冷列表,凡上次在别的启动目录下缓存过的会话全部 miss。这一错位已消除:overlay 现以与会话根同一段 `!!js` 表达式把 `storage-json.root` 锚定到 `$DSH_HOME/storages`(`apps/cli/config/web.cordis.yml`)。残余隐患:`JsonStorageBackend` 仍从不 resolve 根——每次打开 unit 都把路径 join 到当时的 `process.cwd()` 上(packages/storage/storage-json/src/index.ts);出厂 overlay 的根已是绝对路径不受影响,但任何相对根(裸 Loader 启动、测试)仍会被后续 cwd 变化劈开,JSONL 会话后端用「构造时 resolve 一次」防住的正是它("later process.cwd() changes cannot split one backend across roots",packages/session/session-persistence-jsonl/src/index.ts)。 **现在是怎么恢复的。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者今天描述的是愿望而非实现。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。 diff --git a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.i18n.yaml index e252ed8190..d9dd98233e 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md -2026-06-20-assembled-assistant-messages-only.md: ba8135a3d63f292cfedd23de8b4b9d43b4455e8c -2026-06-20-assembled-assistant-messages-only.zh.md: 5d29655a0697d05f20e491d28272898a08ec3e0b +2026-06-20-assembled-assistant-messages-only.md: edc756f71271065ec579a2becb8597190b34cdc8 +2026-06-20-assembled-assistant-messages-only.zh.md: e5f4f8a63a35f5f40c482a4a4855937cc8e6d544 diff --git a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md index ba8135a3d6..edc756f712 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md +++ b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md @@ -19,7 +19,7 @@ ACP `session/load` can replay prior assistant messages as complete content block ## Acceptance criteria - `SessionEventMap` drops `assistant/chunk`, or marks it as non-persisted if a transitional live event is needed. -- [Session persistence docs](../../../../packages/session-persistence/session-persistence/README.md) no longer require every stream chunk to be stored verbatim. +- [Session persistence docs](../../../../packages/session/session-persistence/README.md) no longer require every stream chunk to be stored verbatim. - `llm-replay` and ACP snapshots use an explicit replay fixture format or sidecar for model chunks. - `session/load` renders completed assistant messages from `assistant/message`. - Stored logs get much smaller and remain `seq`-contiguous without chunk holes. diff --git a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.zh.md b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.zh.md index 5d29655a06..e5f4f8a63a 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.zh.md @@ -19,7 +19,7 @@ ACP `session/load` 可以将先前的 assistant 消息作为完整内容块回 ## 验收标准 - `SessionEventMap` 移除 `assistant/chunk`,或在需要过渡性实时事件时将其标记为非持久化。 -- [会话持久化文档](../../../../packages/session-persistence/session-persistence/README.md)不再要求逐字存储每个流式分片。 +- [会话持久化文档](../../../../packages/session/session-persistence/README.md)不再要求逐字存储每个流式分片。 - `llm-replay` 和 ACP 快照使用显式的回放 fixture 格式或伴随文件来存储模型分片。 - `session/load` 从 `assistant/message` 渲染已完成的 assistant 消息。 - 存储的日志大幅缩小,且删除分片后仍保持 `seq` 连续,不留下序号缺口。 diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml index 0ae596bf03..4c5b2d04de 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md -2026-06-20-fold-session-persistence-interface.md: 895b868b2a80d8655284bae1364a85e19e174da7 -2026-06-20-fold-session-persistence-interface.zh.md: d5ce0ed4532706ffadb43460cd0974507393a5ed +2026-06-20-fold-session-persistence-interface.md: c0da160e766cf0bed532f0d11bd52e3a027dbe8b +2026-06-20-fold-session-persistence-interface.zh.md: 93ab244429600bd15e2d8dadfa7dc218e37c2eff diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md index 895b868b2a..c0da160e76 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md @@ -22,7 +22,7 @@ The implementing PR should update the [capability seams](../../implemented/archi - `dsh-session` exports the persistence service type, coordinator, and contract helpers. - JSONL and SQLite backend packages depend on `dsh-session` directly. - `agent-loop` resume uses the session-owned service key. -- [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md), [shared persistence write coordinator](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md), and [package docs](../../../../packages/session-persistence/session-persistence/README.md) explain why backend implementations remain separate. +- [Session persistence](../../implemented/architecture/2026-06-14-session-persistence.md), [shared persistence write coordinator](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md), and [package docs](../../../../packages/session/session-persistence/README.md) explain why backend implementations remain separate. ## What we give up diff --git a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md index d5ce0ed453..93ab244429 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.zh.md @@ -22,7 +22,7 @@ Status: rejected — 独立的持久化接口包是为持久化后端设计的 - `dsh-session` 导出持久化服务类型、协调器和契约辅助工具。 - JSONL 和 SQLite 后端包直接依赖 `dsh-session`。 - `agent-loop` 的恢复功能使用会话包拥有的服务键。 -- [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.md)、[共享持久化写入协调器](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)与[包文档](../../../../packages/session-persistence/session-persistence/README.md)说明后端实现为何仍保持独立。 +- [会话持久化](../../implemented/architecture/2026-06-14-session-persistence.md)、[共享持久化写入协调器](../../implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md)与[包文档](../../../../packages/session/session-persistence/README.md)说明后端实现为何仍保持独立。 ## 放弃了什么 diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml index 09f42a9047..7e9b79fdeb 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md -2026-06-20-truncate-interrupted-turns.md: 3c7acf8d673568f851edd52635a28f73d9bf5f6f -2026-06-20-truncate-interrupted-turns.zh.md: f348e6a99d8214f7968d9f57931f9dddd37973be +2026-06-20-truncate-interrupted-turns.md: c4b0084de537927a27829eb78990c6559bd1df7d +2026-06-20-truncate-interrupted-turns.zh.md: 2d3eca74416eff360029e3ac77f04662878a0084 diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md index 3c7acf8d67..c4b0084de5 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md @@ -21,7 +21,7 @@ This makes the persisted turn boundary simple: a completed `turn/end` is the che - `TurnEndReasonMap` drops the `interrupted` variant. - `interruptedTurnClosers()` and its tests disappear. - The persistence coordinator's repair hook truncates backend-specific torn/open tail state without appending closers. -- [Session persistence docs](../../../../packages/session-persistence/session-persistence/README.md) say load returns the last completed turn, plus no partial final turn. +- [Session persistence docs](../../../../packages/session/session-persistence/README.md) say load returns the last completed turn, plus no partial final turn. - Snapshot and contract tests update together with the behavior they pin. - The session format version and recorded fixtures are refreshed; non-current stored logs are rejected per the pre-release format policy, with no migration path. diff --git a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md index f348e6a99d..2d3eca7441 100644 --- a/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md +++ b/.agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.zh.md @@ -21,7 +21,7 @@ Status: rejected — 单个轮次可以包含大量真实工作,包括多个 - `TurnEndReasonMap` 移除 `interrupted` 变体。 - `interruptedTurnClosers()` 及其测试删除。 - 持久化协调器的修复钩子截断后端特有的撕裂或未关闭的尾部状态,不追加关闭事件。 -- [会话持久化文档](../../../../packages/session-persistence/session-persistence/README.md)说明加载返回最后一个已完成的轮次,不包含部分最终轮次。 +- [会话持久化文档](../../../../packages/session/session-persistence/README.md)说明加载返回最后一个已完成的轮次,不包含部分最终轮次。 - 快照与契约测试随其所固定的行为一同更新。 - 会话格式版本与记录的 fixture(测试前置数据)刷新;按预发布格式策略,非当前版本的存储日志被拒绝,不提供迁移路径。 diff --git a/AGENTS.md b/AGENTS.md index 47a90f0065..7727539bd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,7 +33,7 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// guard/ loop-hygiene plugins cordis/ self-referential toolset: the agent inspects/mounts plugins in its own runtime hooks/ Claude Code/Codex hook bridges + shared wire-protocol library - session-persistence/ persistence seam + JSONL/SQLite backends + session/ durable session data plane: persistence, projection, titles, reporting settings/ user-settings seam + file-backed provider credentials/ credential-reference seam + env-over-.env provider acp/ automation-only Agent Client Protocol server diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index e0ddc02423..4d10310333 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: e88bdac118e382d91c41c471d03182952d8a4508 -architecture.zh.md: 9314a3fbfc87d467a0f902fa1dae9e8f07e2eb28 +architecture.md: c14d68c47f6ed0786e8fc0b0650dbc67678250fb +architecture.zh.md: df484762d29e170a5b1ba64f0637c5170c2c5ef4 diff --git a/docs/architecture.md b/docs/architecture.md index e88bdac118..c14d68c47f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -41,9 +41,9 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services, | `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | background task registry, generic `task_*` controls | | `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | script-driven multi-agent orchestration | | `ctx.goals` | [`goal/`](../packages/goal/README.md) | persisted same-session goals | -| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | durable session-log storage | +| `ctx.sessionPersistence` | [`session/`](../packages/session/README.md) | durable session-log storage | | `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | live-preferred exact/filter/trace queries over SQLite FTS, workspace-authorized model tools | -| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | log-backed fallbacks, one optional asynchronous provider | +| `ctx.sessionTitle` | [`session/session-title`](../packages/session/README.md) | log-backed fallbacks, one optional asynchronous provider | | `ctx.settings` | [`settings/`](../packages/settings/README.md) | per-plugin user-settings namespaces layered over composition entries | | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | named secret references resolved per operation, never inlined in configuration | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI-host directory picking (`native`/`browse` interactions) | diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 9314a3fbfc..df484762d2 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -41,9 +41,9 @@ | `ctx.tasks` | [`tasks/`](../packages/tasks/README.md) | 后台任务注册表和通用 `task_*` 控制 | | `ctx.workflows` | [`workflow/`](../packages/workflow/README.md) | 脚本驱动的多 agent 编排 | | `ctx.goals` | [`goal/`](../packages/goal/README.md) | 持久化的同会话目标 | -| `ctx.sessionPersistence` | [`session-persistence/`](../packages/session-persistence/README.md) | 会话日志的持久化存储 | +| `ctx.sessionPersistence` | [`session/`](../packages/session/README.md) | 会话日志的持久化存储 | | `ctx.sessionQuery` | [`session-query/`](../packages/session-query/README.md) | 基于 SQLite 全文搜索的实时优先精确检索/过滤/追踪、经工作区授权的模型工具 | -| `ctx.sessionTitle` | [`session-title/`](../packages/session-title/README.md) | 基于日志的回退标题和单个可选异步提供方 | +| `ctx.sessionTitle` | [`session/session-title`](../packages/session/README.md) | 基于日志的回退标题和单个可选异步提供方 | | `ctx.settings` | [`settings/`](../packages/settings/README.md) | 按插件划分的用户设置命名空间,分层叠加在装配条目之上 | | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | 具名密钥引用,按操作解析,绝不内联进配置 | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI 宿主目录选取(`native`/`browse` 交互) | diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 4ed8a260d4..60a19999f3 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -374,27 +374,27 @@ flowchart LR | `ctx.llm` | `seam` | [`llm`](../packages/llm/llm) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), [`llm-replay`](../packages/support/llm-replay) | [`agent-loop`](../packages/core/agent-loop), [`compact-basic`](../packages/compact/compact-basic) | - | Adapters register provider implementations; the loop and compaction call the provider-neutral stream service. | | `ctx.tokenMeter` | `core` | [`token-meter`](../packages/llm/token-meter) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Owns isolated per-session replay folds; pressure consumers share immutable revisioned measurements. | | `ctx.toolResultPrune` | `core` | [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. | -| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. | +| `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. | | `ctx.invariants` | `core` | [`invariants`](../packages/support/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. | | `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. | | `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. | -| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | +| `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session/session-persistence) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | | `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-local`](../packages/settings/settings-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer. | | `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage. | -| `ctx.telemetry` | `seam` | [`session-telemetry`](../packages/telemetry/session-telemetry) | [`session-telemetry-otel`](../packages/telemetry/session-telemetry-otel) | - | - | The seam captures, redacts, and hands session records to one backend; nothing else consumes the service — its output leaves the process. | +| `ctx.telemetry` | `seam` | [`session-telemetry`](../packages/session/session-telemetry) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | - | - | The seam captures, redacts, and hands session records to one backend; nothing else consumes the service — its output leaves the process. | | `ctx.storage` | `seam` | [`storage`](../packages/storage/storage) | [`storage-json`](../packages/storage/storage-json), [`storage-sqlite`](../packages/storage/storage-sqlite) | [`storage-domain`](../packages/storage/storage-domain) | - | Backends register side by side under names; data forms (domain first) mount on the hub and translate typed operations into opaque KV-unit primitives. | | `ctx.storageDomain` | `core` | [`storage-domain`](../packages/storage/storage-domain) | - | [`workspace`](../packages/workspace/workspace) | - | Waits for every configured backend, then publishes the domain form as one lifecycle-bound service for typed durable state. | | `ctx.workspace` | `core` | [`workspace`](../packages/workspace/workspace) | - | `apiproxy` | - | Owns WorkspaceId-branded records over the domain facility; stable sessionIds accounts drive Host RPC and GUI projections. | | `ctx.sessionQuery` | `seam` | [`session-query`](../packages/session-query/session-query) | [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | [`session-reference`](../packages/context/session-reference), [`tool-session-query`](../packages/session-query/tool-session-query) | - | The interface supplies exact reads, filters, and traces; its concrete backend adds full-text reconciliation, ranking, snippets, and cursor generations, while the model consumer owns workspace authority and cursor-free rendering. | | `ctx.sessionReferences` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax. | -| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session-title/session-title) | [`session-title-first-message-llm`](../packages/session-title/session-title-first-message-llm), [`session-title-all-messages-llm`](../packages/session-title/session-title-all-messages-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | +| `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm), [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | | `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | | `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | | `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | - | [`tool-ask-user`](../packages/ui/tool-ask-user) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. | | `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions. | | `ctx.commands` | `core` | [`commands`](../packages/ui/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | -| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session-projection/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session-title/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. | -| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session-projection/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. | +| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. | +| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. | | `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | | `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. | | `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. | diff --git a/docs/core-data-structures/persistence.i18n.yaml b/docs/core-data-structures/persistence.i18n.yaml index fe28a36f40..eccd3f146e 100644 --- a/docs/core-data-structures/persistence.i18n.yaml +++ b/docs/core-data-structures/persistence.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/persistence.md -persistence.md: 3d2bc7ff3b45cd3f38fca0b880a0df9adb928bad -persistence.zh.md: 035f46d76e0189bd686e991c3d56d486b6f309d5 +persistence.md: 177d596d3d01134266f139b47430e1e5064d04d6 +persistence.zh.md: 6582c0d7d02d1976d5c3ca3322284098130ed4ad diff --git a/docs/core-data-structures/persistence.md b/docs/core-data-structures/persistence.md index 3d2bc7ff3b..177d596d3d 100644 --- a/docs/core-data-structures/persistence.md +++ b/docs/core-data-structures/persistence.md @@ -4,7 +4,7 @@ English | [中文](persistence.zh.md) The **durability seam** for the event log. [session.md](session.md) describes the in-memory `Session` — the append-only `SessionEvent` log that is the source of truth. This page describes how that log is made durable: the abstract `SessionPersistence` service, its backends, the flush checkpoint, crash recovery, and the metadata header that travels alongside the log. The event vocabulary the log carries is enumerated, member by member, in the generated [persistence log event catalog](../persistence-catalog.md). -The seam is a textbook [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](../../packages/session-persistence/session-persistence), `ctx.sessionPersistence`) defining locate/create/append, reusable Session preparation, logical load/inspect, physical suffix reads, and lightweight list/snapshot observation over the existing `SessionEvent` — **no parallel persisted event type** — and two interchangeable backends implementing the same contract. See the [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). +The seam is a textbook [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): one abstract service ([dsh-session-persistence](../../packages/session/session-persistence), `ctx.sessionPersistence`) defining locate/create/append, reusable Session preparation, logical load/inspect, physical suffix reads, and lightweight list/snapshot observation over the existing `SessionEvent` — **no parallel persisted event type** — and two interchangeable backends implementing the same contract. See the [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md). ## The flush checkpoint @@ -202,5 +202,5 @@ interface SessionPersistenceSnapshot { Both implement the same abstract `SessionPersistence` (locate/create/append/prepare/load/inspect/readFrom/list/listSnapshots over `SessionEvent`, with optional cancellation on observation methods) and pass `runPersistenceContract`, proving the seam is genuinely backend-agnostic: -- **[dsh-session-persistence-jsonl](../../packages/session-persistence/session-persistence-jsonl)** — an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic writes, interrupted-turn recovery, and a read/replay path. -- **[dsh-session-persistence-sqlite](../../packages/session-persistence/session-persistence-sqlite)** — `node:sqlite`, one row per `SessionEvent`. The row shape `(session_id, seq, type, time, data, source_event_seqs, surface_op)` maps 1:1 onto the event, including optional surface metadata, so there is no parallel persisted schema to keep in sync. +- **[dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl)** — an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic writes, interrupted-turn recovery, and a read/replay path. +- **[dsh-session-persistence-sqlite](../../packages/session/session-persistence-sqlite)** — `node:sqlite`, one row per `SessionEvent`. The row shape `(session_id, seq, type, time, data, source_event_seqs, surface_op)` maps 1:1 onto the event, including optional surface metadata, so there is no parallel persisted schema to keep in sync. diff --git a/docs/core-data-structures/persistence.zh.md b/docs/core-data-structures/persistence.zh.md index 035f46d76e..6582c0d7d0 100644 --- a/docs/core-data-structures/persistence.zh.md +++ b/docs/core-data-structures/persistence.zh.md @@ -4,7 +4,7 @@ 事件日志的**持久性 seam**。[session.md](session.md) 描述了内存中的 `Session`:仅追加的 `SessionEvent` 日志即为真源。本页描述如何使该日志持久化:抽象的 `SessionPersistence` 服务、它的后端、flush 检查点、崩溃恢复,以及随日志一同存储的元数据头。日志承载的事件词汇在生成的[持久化日志事件目录](../persistence-catalog.md)中逐项列举。 -该 seam 是典型的[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md):一个抽象服务([dsh-session-persistence](../../packages/session-persistence/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上定义 locate/create/append、可复用的 Session 准备流程、逻辑 load/inspect、物理后缀读取,以及轻量的 list/snapshot 观察——**没有平行的持久化事件类型**——以及两个实现同一契约的可互换后端。见 [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)。 +该 seam 是典型的[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md):一个抽象服务([dsh-session-persistence](../../packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上定义 locate/create/append、可复用的 Session 准备流程、逻辑 load/inspect、物理后缀读取,以及轻量的 list/snapshot 观察——**没有平行的持久化事件类型**——以及两个实现同一契约的可互换后端。见 [session-persistence Agent Note](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)。 ## flush 检查点 @@ -202,5 +202,5 @@ interface SessionPersistenceSnapshot { 两者都实现同一个抽象 `SessionPersistence`(在 `SessionEvent` 上执行 locate/create/append/prepare/load/inspect/readFrom/list/listSnapshots,观察方法可选支持取消),并通过 `runPersistenceContract`,证明该 seam 确实与后端无关: -- **[dsh-session-persistence-jsonl](../../packages/session-persistence/session-persistence-jsonl)**——每个会话一份仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;支持崩溃安全的原子写入、被中断轮次的恢复以及读取/回放路径。 -- **[dsh-session-persistence-sqlite](../../packages/session-persistence/session-persistence-sqlite)**:基于 `node:sqlite`,每个 `SessionEvent` 一行。行结构 `(session_id, seq, type, time, data, source_event_seqs, surface_op)` 与事件 1:1 映射(包含可选的 surface 元数据),因此没有需要保持同步的并行持久化 schema。 +- **[dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl)**——每个会话一份仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;支持崩溃安全的原子写入、被中断轮次的恢复以及读取/回放路径。 +- **[dsh-session-persistence-sqlite](../../packages/session/session-persistence-sqlite)**:基于 `node:sqlite`,每个 `SessionEvent` 一行。行结构 `(session_id, seq, type, time, data, source_event_seqs, surface_op)` 与事件 1:1 映射(包含可选的 surface 元数据),因此没有需要保持同步的并行持久化 schema。 diff --git a/docs/core-data-structures/session-title.i18n.yaml b/docs/core-data-structures/session-title.i18n.yaml index 67f9ac765b..ed9955472c 100644 --- a/docs/core-data-structures/session-title.i18n.yaml +++ b/docs/core-data-structures/session-title.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/session-title.md -session-title.md: fff1aa1f6be45d0cfc4d7f6a9527ccb93561618f -session-title.zh.md: 77a0a5e94053c94bf84bbc749cb6e260898b5d00 +session-title.md: 7bced67f07df3f00471f02b766922cdc3e4fd29e +session-title.zh.md: e8e501d3aafee49395da3d9a8efd4e91a724f09e diff --git a/docs/core-data-structures/session-title.md b/docs/core-data-structures/session-title.md index fff1aa1f6b..7bced67f07 100644 --- a/docs/core-data-structures/session-title.md +++ b/docs/core-data-structures/session-title.md @@ -2,9 +2,9 @@ English | [中文](session-title.zh.md) -Durable latest-wins title state and the optional asynchronous provider vocabulary owned by [`@deepseek-ai/dsh-session-title`](../../packages/session-title/session-title). The shared LLM helper owns the exact auxiliary request record. Package READMEs own timing, fallback, failure, and fork behavior; the generated [persistence catalog](../persistence-catalog.md) owns the complete event declarations. +Durable latest-wins title state and the optional asynchronous provider vocabulary owned by [`@deepseek-ai/dsh-session-title`](../../packages/session/session-title). The shared LLM helper owns the exact auxiliary request record. Package READMEs own timing, fallback, failure, and fork behavior; the generated [persistence catalog](../persistence-catalog.md) owns the complete event declarations. -Sources: [`packages/session-title/session-title/src/index.ts`](../../packages/session-title/session-title/src/index.ts), [`packages/session-title/session-title-llm/src/index.ts`](../../packages/session-title/session-title-llm/src/index.ts) +Sources: [`packages/session/session-title/src/index.ts`](../../packages/session/session-title/src/index.ts), [`packages/session/session-title-llm/src/index.ts`](../../packages/session/session-title-llm/src/index.ts) ## Durable title state diff --git a/docs/core-data-structures/session-title.zh.md b/docs/core-data-structures/session-title.zh.md index 77a0a5e940..e8e501d3aa 100644 --- a/docs/core-data-structures/session-title.zh.md +++ b/docs/core-data-structures/session-title.zh.md @@ -2,9 +2,9 @@ [English](session-title.md) | 中文 -[`@deepseek-ai/dsh-session-title`](../../packages/session-title/session-title) 所拥有的持久、后写覆盖的标题状态与可选异步提供方词汇。共享 LLM(大语言模型)辅助组件负责精确的辅助请求记录。各包 README 负责时序、回退、失败与 fork 行为;生成的[持久化日志事件目录](../persistence-catalog.md)负责完整的事件声明。 +[`@deepseek-ai/dsh-session-title`](../../packages/session/session-title) 所拥有的持久、后写覆盖的标题状态与可选异步提供方词汇。共享 LLM(大语言模型)辅助组件负责精确的辅助请求记录。各包 README 负责时序、回退、失败与 fork 行为;生成的[持久化日志事件目录](../persistence-catalog.md)负责完整的事件声明。 -源码:[`packages/session-title/session-title/src/index.ts`](../../packages/session-title/session-title/src/index.ts)、[`packages/session-title/session-title-llm/src/index.ts`](../../packages/session-title/session-title-llm/src/index.ts) +源码:[`packages/session/session-title/src/index.ts`](../../packages/session/session-title/src/index.ts)、[`packages/session/session-title-llm/src/index.ts`](../../packages/session/session-title-llm/src/index.ts) ## 持久标题状态 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 3845c2cb1a..daf2ea7ab2 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -557,7 +557,7 @@ Source: [`packages/core/session/src/types.ts:304`](../packages/core/session/src/ Types: [SessionTitleEventData](core-data-structures/session-title.md) -Source: [`packages/session-title/session-title/src/index.ts:100`](../packages/session-title/session-title/src/index.ts) +Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts) #### `session/title-llm-request` — log-only @@ -568,7 +568,7 @@ Source: [`packages/session-title/session-title/src/index.ts:100`](../packages/se Types: [SessionTitleLlmRequestEventData](core-data-structures/session-title.md) -Source: [`packages/session-title/session-title-llm/src/index.ts:43`](../packages/session-title/session-title-llm/src/index.ts) +Source: [`packages/session/session-title-llm/src/index.ts:43`](../packages/session/session-title-llm/src/index.ts) ### `step/*` diff --git a/knip.json b/knip.json index d154cd646b..0e82064200 100644 --- a/knip.json +++ b/knip.json @@ -283,7 +283,7 @@ "tests/**/*.ts" ] }, - "packages/telemetry/session-telemetry-otel": { + "packages/session/session-telemetry-otel": { "entry": [ "tests/**/*.spec.ts", "tests/**/*.e2e.ts" @@ -414,7 +414,7 @@ "tests/**/*.ts" ] }, - "packages/session-title/session-title-first-message-llm": { + "packages/session/session-title-first-message-llm": { "entry": [ "tests/**/*.spec.ts", "tests/**/*.e2e.ts" @@ -434,7 +434,7 @@ "tests/**/*.ts" ] }, - "packages/session-persistence/session-checkpoint-policy": { + "packages/session/session-checkpoint-policy": { "entry": [ "tests/**/*.spec.ts", "tests/**/*.e2e.ts" diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 143e7704c2..44dd906b76 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: 98fc87a6b26a42c58bb63cba96cd6af384f3ec71 -README.zh.md: 86a316dca77efd5e5eb59e3fca7d53890a34901d +README.md: 9f8b3f936af6392239218d5389c91c87d0f7d1f9 +README.zh.md: 2f4e86cb541a8a34762b36cac57f456de96cac5d diff --git a/packages/README.md b/packages/README.md index 98fc87a6b2..9f8b3f936a 100644 --- a/packages/README.md +++ b/packages/README.md @@ -39,13 +39,10 @@ Groups hold `packages///`; names stay `@deepseek-ai/dsh-`. **Gr | [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | Product — stable surface | | [`cordis/`](cordis/README.md) | Cordis runtime integration: self-inspection, temporary Plugins, restricted repository Plugin loading | Product — stable surface | | [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | Product — stable surface | -| [`session-persistence/`](session-persistence/README.md) | Persistence seam + JSONL/SQLite backends | Product — stable surface | -| [`session-projection/`](session-projection/README.md) | Projection seam: domain fold units serve whole values | Product — stable surface | +| [`session/`](session/README.md) | Durable session data plane: persistence seam + JSONL/SQLite backends, projection seam, log-backed titles, session reporting | Product — stable surface | | [`session-query/`](session-query/README.md) | Session retrieval family: logical corpus, bounded reads, lineage, event relationships, semantic filtering, and SQLite full-text search | Product — stable surface | -| [`session-title/`](session-title/README.md) | Log-backed session titles: fallback service and opt-in LLM providers | Product — stable surface | | [`settings/`](settings/README.md) | User-settings seam + file-backed provider | Product — stable surface | | [`credentials/`](credentials/README.md) | Credential-reference seam + env-over-`.env` provider | Product — stable surface | -| [`telemetry/`](telemetry/README.md) | Session reporting: capture/redact seam, OTel backend | Product — stable surface | | [`storage/`](storage/README.md) | Non-session storage hub + backends + domain form | Product — stable surface | | [`workspace/`](workspace/README.md) | Workspace entity | Product — stable surface | | [`sdk/`](sdk/README.md) | Project SDK tooling | Product — stable surface | diff --git a/packages/README.zh.md b/packages/README.zh.md index 86a316dca7..2f4e86cb54 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -39,13 +39,10 @@ | [`bundle/`](bundle/README.md) | 可安装的 `dsh --profile` 补丁层 | 产品:稳定表面 | | [`cordis/`](cordis/README.md) | Cordis 运行时集成:自检、临时 Plugin、受限 repository Plugin 加载 | 产品:稳定表面 | | [`hooks/`](hooks/README.md) | 钩子桥接 + 共享 Claude Code/Codex 协议格式库 | 产品:稳定表面 | -| [`session-persistence/`](session-persistence/README.md) | 持久化 seam + JSONL/SQLite 后端 | 产品:稳定表面 | -| [`session-projection/`](session-projection/README.md) | 投影 seam:领域折叠单元供给全量值 | 产品:稳定表面 | +| [`session/`](session/README.md) | 持久会话数据平面:持久化 seam + JSONL/SQLite 后端、投影 seam、日志支持的标题、会话上报 | 产品:稳定表面 | | [`session-query/`](session-query/README.md) | 会话检索系列:逻辑语料库、有界读取、血缘、事件关系、语义过滤和 SQLite 全文搜索 | 产品:稳定表面 | -| [`session-title/`](session-title/README.md) | 日志支撑的会话标题:回退服务与选用 LLM 提供方 | 产品:稳定表面 | | [`settings/`](settings/README.md) | 用户设置 seam + 文件 provider | 产品:稳定表面 | | [`credentials/`](credentials/README.md) | 凭据引用 seam + 环境叠加 `.env` provider | 产品:稳定表面 | -| [`telemetry/`](telemetry/README.md) | 会话上报:捕获/脱敏 seam、OTel 后端 | 产品:稳定表面 | | [`storage/`](storage/README.md) | 非会话存储中枢 + 后端 + 领域形式 | 产品:稳定表面 | | [`workspace/`](workspace/README.md) | Workspace 实体 | 产品:稳定表面 | | [`sdk/`](sdk/README.md) | 项目 SDK 工具 | 产品:稳定表面 | diff --git a/packages/client/runtime/tsconfig.json b/packages/client/runtime/tsconfig.json index f93546e855..71cfb229a9 100644 --- a/packages/client/runtime/tsconfig.json +++ b/packages/client/runtime/tsconfig.json @@ -30,10 +30,10 @@ "path": "../../compact/compact" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { - "path": "../../session-title/session-title" + "path": "../../session/session-title" }, { "path": "../../llm/llm" diff --git a/packages/client/ui-conversation/tsconfig.json b/packages/client/ui-conversation/tsconfig.json index 04b265bdd5..0505786841 100644 --- a/packages/client/ui-conversation/tsconfig.json +++ b/packages/client/ui-conversation/tsconfig.json @@ -24,7 +24,7 @@ "path": "../runtime" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../llm/token-meter" diff --git a/packages/core/agent-loop/tsconfig.json b/packages/core/agent-loop/tsconfig.json index 0949f18453..c3504e4eb4 100644 --- a/packages/core/agent-loop/tsconfig.json +++ b/packages/core/agent-loop/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../core/system-prompt" diff --git a/packages/examples/acp-demo/tests/built-bin.e2e.ts b/packages/examples/acp-demo/tests/built-bin.e2e.ts index 6f67baa5a5..d09a0e333d 100644 --- a/packages/examples/acp-demo/tests/built-bin.e2e.ts +++ b/packages/examples/acp-demo/tests/built-bin.e2e.ts @@ -34,8 +34,8 @@ const dshPackages = [ 'examples/agent-spine-demo', 'core/agent', 'core/session', 'core/system-prompt', 'core/tools', 'core/agent-loop', 'llm/llm', 'bash/bash', 'bash/bash-local', 'bash/tool-bash', 'subprocess/subprocess', 'subprocess/subprocess-local', 'context/workspace-context', 'support/invariants', 'ui/app-boot', - 'session-persistence/session-persistence', - 'session-persistence/session-checkpoint-policy', 'session-persistence/session-persistence-jsonl', + 'session/session-persistence', + 'session/session-checkpoint-policy', 'session/session-persistence-jsonl', 'acp/acp', 'examples/acp-demo', 'util/paths', ] const vendorPackages = [ diff --git a/packages/examples/acp-demo/tsconfig.json b/packages/examples/acp-demo/tsconfig.json index 6eeffc5eef..34b48fc46a 100644 --- a/packages/examples/acp-demo/tsconfig.json +++ b/packages/examples/acp-demo/tsconfig.json @@ -39,10 +39,10 @@ "path": "../../context/workspace-context" }, { - "path": "../../session-persistence/session-checkpoint-policy" + "path": "../../session/session-checkpoint-policy" }, { - "path": "../../session-persistence/session-persistence-jsonl" + "path": "../../session/session-persistence-jsonl" }, { "path": "../../support/invariants" diff --git a/packages/examples/agent-spine-demo/tsconfig.json b/packages/examples/agent-spine-demo/tsconfig.json index f245e9f4d0..e31863d3b1 100644 --- a/packages/examples/agent-spine-demo/tsconfig.json +++ b/packages/examples/agent-spine-demo/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../core/session" }, { - "path": "../../session-title/session-title" + "path": "../../session/session-title" }, { "path": "../../core/system-prompt" diff --git a/packages/goal/goal/tsconfig.json b/packages/goal/goal/tsconfig.json index f106707bd3..1484f5a4b9 100644 --- a/packages/goal/goal/tsconfig.json +++ b/packages/goal/goal/tsconfig.json @@ -33,7 +33,7 @@ "path": "../../core/agent" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../typert/type-meta" diff --git a/packages/hooks/hooks-claude/tsconfig.json b/packages/hooks/hooks-claude/tsconfig.json index 445d0f68b1..e254c7146f 100644 --- a/packages/hooks/hooks-claude/tsconfig.json +++ b/packages/hooks/hooks-claude/tsconfig.json @@ -30,7 +30,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../subagent/subagent" diff --git a/packages/hooks/hooks-codex/tsconfig.json b/packages/hooks/hooks-codex/tsconfig.json index 3bd9bd91e5..46e8216404 100644 --- a/packages/hooks/hooks-codex/tsconfig.json +++ b/packages/hooks/hooks-codex/tsconfig.json @@ -30,7 +30,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../llm/llm" diff --git a/packages/host/apiproxy/tsconfig.json b/packages/host/apiproxy/tsconfig.json index 8686a9e468..58c63f5669 100644 --- a/packages/host/apiproxy/tsconfig.json +++ b/packages/host/apiproxy/tsconfig.json @@ -42,19 +42,19 @@ "path": "../../core/tools" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { - "path": "../../session-projection/session-projection-cache" + "path": "../../session/session-projection-cache" }, { "path": "../../session-query/session-query" }, { - "path": "../../session-title/session-title" + "path": "../../session/session-title" }, { "path": "../../session-query/session-query" diff --git a/packages/llm/token-meter/tsconfig.json b/packages/llm/token-meter/tsconfig.json index b8b32e36fa..404607f87a 100644 --- a/packages/llm/token-meter/tsconfig.json +++ b/packages/llm/token-meter/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../compact/compact" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../support/invariants" diff --git a/packages/plan/plan-mode/tsconfig.json b/packages/plan/plan-mode/tsconfig.json index a9b2d4dccf..6fa3cffbbc 100644 --- a/packages/plan/plan-mode/tsconfig.json +++ b/packages/plan/plan-mode/tsconfig.json @@ -33,7 +33,7 @@ "path": "../../ui/commands" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../support/invariants" diff --git a/packages/sdk/helper/tsconfig.json b/packages/sdk/helper/tsconfig.json index 528b370a5c..f33033f47f 100644 --- a/packages/sdk/helper/tsconfig.json +++ b/packages/sdk/helper/tsconfig.json @@ -19,10 +19,10 @@ "path": "../../hooks/hooks-codex" }, { - "path": "../../session-persistence/session-persistence-jsonl" + "path": "../../session/session-persistence-jsonl" }, { - "path": "../../session-persistence/session-persistence-sqlite" + "path": "../../session/session-persistence-sqlite" }, { "path": "../../subagent/tool-subagent" diff --git a/packages/session-persistence/README.i18n.yaml b/packages/session-persistence/README.i18n.yaml deleted file mode 100644 index 85eb2bd27d..0000000000 --- a/packages/session-persistence/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/session-persistence/README.md -README.md: 060f757b3568318b0be4b01b3a10d019d28eac3b -README.zh.md: 67022565f7dabcbbccee30d5ce60fa559088ca39 diff --git a/packages/session-persistence/README.md b/packages/session-persistence/README.md deleted file mode 100644 index 060f757b35..0000000000 --- a/packages/session-persistence/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# session-persistence/ — persistence capability family - -English | [中文](README.zh.md) - -This family defines durable session persistence, semantic checkpoint policy, and the shipped storage backends. - -| Package | Role | ctx key | -|---|---|---| -| [`session-persistence/`](session-persistence/README.md) | Defines the persistence service and shared write coordination | `ctx.sessionPersistence` | -| [`session-checkpoint-policy/`](session-checkpoint-policy/README.md) | Applies semantic durability checkpoints | wraps `ctx.llm` and `ctx.tools` | -| [`session-persistence-jsonl/`](session-persistence-jsonl/README.md) | Persists sessions in JSONL files | registers on `ctx.sessionPersistence` | -| [`session-persistence-sqlite/`](session-persistence-sqlite/README.md) | Persists sessions in SQLite | registers on `ctx.sessionPersistence` | - -The [session-persistence decision](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) records the family design. diff --git a/packages/session-persistence/README.zh.md b/packages/session-persistence/README.zh.md deleted file mode 100644 index 67022565f7..0000000000 --- a/packages/session-persistence/README.zh.md +++ /dev/null @@ -1,14 +0,0 @@ -# session-persistence/:持久化能力家族 - -[English](README.md) | 中文 - -本家族定义持久会话数据的持久化机制、语义检查点策略以及随产品交付的存储后端。 - -| 包 | 职责 | ctx 键 | -|---|---|---| -| [`session-persistence/`](session-persistence/README.md) | 定义持久化服务和共享写入协调机制 | `ctx.sessionPersistence` | -| [`session-checkpoint-policy/`](session-checkpoint-policy/README.md) | 应用语义持久性检查点 | 包装 `ctx.llm` 和 `ctx.tools` | -| [`session-persistence-jsonl/`](session-persistence-jsonl/README.md) | 将会话持久化到 JSONL 文件 | 注册到 `ctx.sessionPersistence` | -| [`session-persistence-sqlite/`](session-persistence-sqlite/README.md) | 将会话持久化到 SQLite | 注册到 `ctx.sessionPersistence` | - -[会话持久化决策](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)记录了该家族的设计。 diff --git a/packages/session-projection/README.i18n.yaml b/packages/session-projection/README.i18n.yaml deleted file mode 100644 index 5e03069b99..0000000000 --- a/packages/session-projection/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/session-projection/README.md -README.md: f1fc8337bbea6663915c19f39fdf02f14190370d -README.zh.md: 3323166f496fabafabcd96abeaec707a28ce552a diff --git a/packages/session-projection/README.md b/packages/session-projection/README.md deleted file mode 100644 index f1fc8337bb..0000000000 --- a/packages/session-projection/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# session-projection/ — session projection capability family - -English | [中文](README.zh.md) - -This family serves current, log-derived per-session state to client carriers. - -| Package | Role | ctx key | -|---|---|---| -| [`session-projection/`](session-projection/README.md) | Defines and drives session projection units | `ctx.sessionProjections` | -| [`session-projection-cache/`](session-projection-cache/README.md) | Persists and restores projection checkpoints | `ctx.sessionProjectionCache` | diff --git a/packages/session-projection/README.zh.md b/packages/session-projection/README.zh.md deleted file mode 100644 index 3323166f49..0000000000 --- a/packages/session-projection/README.zh.md +++ /dev/null @@ -1,10 +0,0 @@ -# session-projection/:会话投影能力家族 - -[English](README.md) | 中文 - -本家族向客户端载体提供从日志派生的当前逐会话状态。 - -| 包 | 职责 | ctx 键 | -|---|---|---| -| [`session-projection/`](session-projection/README.md) | 定义并驱动会话投影单元 | `ctx.sessionProjections` | -| [`session-projection-cache/`](session-projection-cache/README.md) | 持久化并恢复投影检查点 | `ctx.sessionProjectionCache` | diff --git a/packages/session-query/session-query-sqlite/tsconfig.json b/packages/session-query/session-query-sqlite/tsconfig.json index 1f09565c4a..2a74d8e326 100644 --- a/packages/session-query/session-query-sqlite/tsconfig.json +++ b/packages/session-query/session-query-sqlite/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/session-query/session-query/tsconfig.json b/packages/session-query/session-query/tsconfig.json index 0f17353fee..9e661c1c2c 100644 --- a/packages/session-query/session-query/tsconfig.json +++ b/packages/session-query/session-query/tsconfig.json @@ -24,10 +24,10 @@ "path": "../../core/session" }, { - "path": "../../session-title/session-title" + "path": "../../session/session-title" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/session-title/README.md b/packages/session-title/README.md deleted file mode 100644 index c2ccc1cd6d..0000000000 --- a/packages/session-title/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# session-title/ — log-backed session-title capability family - -English | [中文](README.zh.md) - -This family derives durable session titles from the session log, with an optional model-backed provider. - -| Package | Role | ctx key | -|---|---|---| -| [`session-title/`](session-title/README.md) | Owns title state, fallback behavior, provider registration, and refresh | `ctx.sessionTitle` | -| [`session-title-llm/`](session-title-llm/README.md) | Provides shared model-backed title generation | — | -| [`session-title-first-message-llm/`](session-title-first-message-llm/README.md) | Titles a session from its first eligible human message | registers on `ctx.sessionTitle` | -| [`session-title-all-messages-llm/`](session-title-all-messages-llm/README.md) | Titles a session from all eligible human messages | registers on `ctx.sessionTitle` | - -Deployments may register one model-backed provider; the service retains a deterministic fallback when none is present. diff --git a/packages/session-title/README.zh.md b/packages/session-title/README.zh.md deleted file mode 100644 index b8df266a86..0000000000 --- a/packages/session-title/README.zh.md +++ /dev/null @@ -1,14 +0,0 @@ -# session-title/:日志支持的会话标题能力族 - -[English](README.md) | 中文 - -该包族从会话日志派生持久会话标题,并支持可选的模型后端 provider。 - -| 包 | 职责 | ctx key | -|---|---|---| -| [`session-title/`](session-title/README.md) | 负责标题状态、回退行为、provider 注册与刷新 | `ctx.sessionTitle` | -| [`session-title-llm/`](session-title-llm/README.md) | 提供共享的模型标题生成能力 | — | -| [`session-title-first-message-llm/`](session-title-first-message-llm/README.md) | 根据第一条合格的人类消息生成会话标题 | 注册到 `ctx.sessionTitle` | -| [`session-title-all-messages-llm/`](session-title-all-messages-llm/README.md) | 根据所有合格的人类消息生成会话标题 | 注册到 `ctx.sessionTitle` | - -部署可注册一个模型后端 provider;未注册时,服务仍提供确定性回退。 diff --git a/packages/session-title/README.i18n.yaml b/packages/session/README.i18n.yaml similarity index 71% rename from packages/session-title/README.i18n.yaml rename to packages/session/README.i18n.yaml index 2c5d1f2788..b500a64611 100644 --- a/packages/session-title/README.i18n.yaml +++ b/packages/session/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/session-title/README.md -README.md: c2ccc1cd6d329a58f673a9ea5d47ce2f35c9dc41 -README.zh.md: b8df266a861da5cb20a76d0738bba4993218c3d5 +# pnpm run verify-translation-pairing --write packages/session/README.md +README.md: 45eff16ad57ec1ccc8ebdd55db82f41b5adfc111 +README.zh.md: 2d67e28ef169f4e4376b2a644a3b842e5835df04 diff --git a/packages/session/README.md b/packages/session/README.md new file mode 100644 index 0000000000..45eff16ad5 --- /dev/null +++ b/packages/session/README.md @@ -0,0 +1,49 @@ +# session/ — durable session data plane + +English | [中文](README.zh.md) + +The durable family around `core/session`'s live in-memory service: the persistence seam with its storage backends and checkpoint policy, the projection seam that serves whole log-derived values, log-backed titles, and outbound session telemetry. All **product** packages. `session-query/` remains a sibling group: the read/tool surface is consumed independently of persistence internals. + +## Persistence + +Durable session persistence, semantic checkpoint policy, and the shipped storage backends. + +| Package | Role | ctx key | +|---|---|---| +| [`session-persistence/`](session-persistence/README.md) | Defines the persistence service and shared write coordination | `ctx.sessionPersistence` | +| [`session-checkpoint-policy/`](session-checkpoint-policy/README.md) | Applies semantic durability checkpoints | wraps `ctx.llm` and `ctx.tools` | +| [`session-persistence-jsonl/`](session-persistence-jsonl/README.md) | Persists sessions in JSONL files | registers on `ctx.sessionPersistence` | +| [`session-persistence-sqlite/`](session-persistence-sqlite/README.md) | Persists sessions in SQLite | registers on `ctx.sessionPersistence` | + +The [session-persistence decision](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md) records the persistence design. + +## Projection + +Serves current, log-derived per-session state to client carriers. + +| Package | Role | ctx key | +|---|---|---| +| [`session-projection/`](session-projection/README.md) | Defines and drives session projection units | `ctx.sessionProjections` | +| [`session-projection-cache/`](session-projection-cache/README.md) | Persists and restores projection checkpoints | `ctx.sessionProjectionCache` | + +## Titles + +Derives durable session titles from the session log, with an optional model-backed provider. + +| Package | Role | ctx key | +|---|---|---| +| [`session-title/`](session-title/README.md) | Owns title state, fallback behavior, provider registration, and refresh | `ctx.sessionTitle` | +| [`session-title-llm/`](session-title-llm/README.md) | Provides shared model-backed title generation | — | +| [`session-title-first-message-llm/`](session-title-first-message-llm/README.md) | Titles a session from its first eligible human message | registers on `ctx.sessionTitle` | +| [`session-title-all-messages-llm/`](session-title-all-messages-llm/README.md) | Titles a session from all eligible human messages | registers on `ctx.sessionTitle` | + +Deployments may register one model-backed provider; the service retains a deterministic fallback when none is present. + +## Telemetry + +Projects session activity into outbound telemetry and delegates delivery to a configured reporting backend. The [telemetry decision](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md) records the reporting boundary; the [mode decision](../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.md) records immediate, feedback-gated, and disabled delivery. + +| Package | Role | +|---|---| +| [`session-telemetry/`](session-telemetry/README.md) | Defines capture, redaction, projection, and live or on-demand backend delivery. | +| [`session-telemetry-otel/`](session-telemetry-otel/README.md) | Delivers telemetry through OpenTelemetry logs in `FULL`, `FEEDBACK_ONLY`, or `DISABLED` mode. | diff --git a/packages/session/README.zh.md b/packages/session/README.zh.md new file mode 100644 index 0000000000..2d67e28ef1 --- /dev/null +++ b/packages/session/README.zh.md @@ -0,0 +1,49 @@ +# session/:持久会话数据平面 + +[English](README.md) | 中文 + +围绕 `core/session` 常驻内存实时服务的持久家族:持久化 seam 连同其存储后端与检查点策略、供出日志派生全量值的投影 seam、日志支持的标题,以及外发会话遥测。全部都是**产品**包(package)。`session-query/` 仍是同级独立组:读取/工具面的消费不依赖持久化内部实现。 + +## 持久化 + +持久会话数据的持久化机制、语义检查点策略以及随产品交付的存储后端。 + +| 包 | 职责 | ctx 键 | +|---|---|---| +| [`session-persistence/`](session-persistence/README.md) | 定义持久化服务和共享写入协调机制 | `ctx.sessionPersistence` | +| [`session-checkpoint-policy/`](session-checkpoint-policy/README.md) | 应用语义持久性检查点 | 包装 `ctx.llm` 和 `ctx.tools` | +| [`session-persistence-jsonl/`](session-persistence-jsonl/README.md) | 将会话持久化到 JSONL 文件 | 注册到 `ctx.sessionPersistence` | +| [`session-persistence-sqlite/`](session-persistence-sqlite/README.md) | 将会话持久化到 SQLite | 注册到 `ctx.sessionPersistence` | + +[会话持久化决策](../../.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)记录了持久化设计。 + +## 投影 + +向客户端载体提供从日志派生的当前逐会话状态。 + +| 包 | 职责 | ctx 键 | +|---|---|---| +| [`session-projection/`](session-projection/README.md) | 定义并驱动会话投影单元 | `ctx.sessionProjections` | +| [`session-projection-cache/`](session-projection-cache/README.md) | 持久化并恢复投影检查点 | `ctx.sessionProjectionCache` | + +## 标题 + +从会话日志派生持久会话标题,并支持可选的模型后端 provider。 + +| 包 | 职责 | ctx 键 | +|---|---|---| +| [`session-title/`](session-title/README.md) | 负责标题状态、回退行为、provider 注册与刷新 | `ctx.sessionTitle` | +| [`session-title-llm/`](session-title-llm/README.md) | 提供共享的模型标题生成能力 | — | +| [`session-title-first-message-llm/`](session-title-first-message-llm/README.md) | 根据第一条合格的人类消息生成会话标题 | 注册到 `ctx.sessionTitle` | +| [`session-title-all-messages-llm/`](session-title-all-messages-llm/README.md) | 根据所有合格的人类消息生成会话标题 | 注册到 `ctx.sessionTitle` | + +部署可注册一个模型后端 provider;未注册时,服务仍提供确定性回退。 + +## 遥测 + +将会话活动投影为外发遥测,并将投递委派给配置的上报后端。[遥测决策](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)记录上报边界;[模式决策](../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.md)记录即时、反馈门控与禁用投递。 + +| 包 | 职责 | +|---|---| +| [`session-telemetry/`](session-telemetry/README.md) | 定义捕获、脱敏、投影,以及实时或按需后端投递。 | +| [`session-telemetry-otel/`](session-telemetry-otel/README.md) | 通过 OpenTelemetry 日志以 `FULL`、`FEEDBACK_ONLY` 或 `DISABLED` 模式投递遥测。 | diff --git a/packages/session-persistence/session-checkpoint-policy/README.i18n.yaml b/packages/session/session-checkpoint-policy/README.i18n.yaml similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/README.i18n.yaml rename to packages/session/session-checkpoint-policy/README.i18n.yaml diff --git a/packages/session-persistence/session-checkpoint-policy/README.md b/packages/session/session-checkpoint-policy/README.md similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/README.md rename to packages/session/session-checkpoint-policy/README.md diff --git a/packages/session-persistence/session-checkpoint-policy/README.zh.md b/packages/session/session-checkpoint-policy/README.zh.md similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/README.zh.md rename to packages/session/session-checkpoint-policy/README.zh.md diff --git a/packages/session-persistence/session-checkpoint-policy/package.json b/packages/session/session-checkpoint-policy/package.json similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/package.json rename to packages/session/session-checkpoint-policy/package.json diff --git a/packages/session-persistence/session-checkpoint-policy/src/index.ts b/packages/session/session-checkpoint-policy/src/index.ts similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/src/index.ts rename to packages/session/session-checkpoint-policy/src/index.ts diff --git a/packages/session-persistence/session-checkpoint-policy/src/invariant.ts b/packages/session/session-checkpoint-policy/src/invariant.ts similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/src/invariant.ts rename to packages/session/session-checkpoint-policy/src/invariant.ts diff --git a/packages/session-persistence/session-checkpoint-policy/tests/crash-recovery.e2e.ts b/packages/session/session-checkpoint-policy/tests/crash-recovery.e2e.ts similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/tests/crash-recovery.e2e.ts rename to packages/session/session-checkpoint-policy/tests/crash-recovery.e2e.ts diff --git a/packages/session-persistence/session-checkpoint-policy/tests/fixtures/crash-child.ts b/packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/tests/fixtures/crash-child.ts rename to packages/session/session-checkpoint-policy/tests/fixtures/crash-child.ts diff --git a/packages/session-persistence/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts b/packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts similarity index 100% rename from packages/session-persistence/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts rename to packages/session/session-checkpoint-policy/tests/session-checkpoint-policy.spec.ts diff --git a/packages/session-persistence/session-checkpoint-policy/tsconfig.json b/packages/session/session-checkpoint-policy/tsconfig.json similarity index 89% rename from packages/session-persistence/session-checkpoint-policy/tsconfig.json rename to packages/session/session-checkpoint-policy/tsconfig.json index 1b81e05951..400f67a53b 100644 --- a/packages/session-persistence/session-checkpoint-policy/tsconfig.json +++ b/packages/session/session-checkpoint-policy/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/session-persistence/session-persistence-jsonl/README.i18n.yaml b/packages/session/session-persistence-jsonl/README.i18n.yaml similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/README.i18n.yaml rename to packages/session/session-persistence-jsonl/README.i18n.yaml diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session/session-persistence-jsonl/README.md similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/README.md rename to packages/session/session-persistence-jsonl/README.md diff --git a/packages/session-persistence/session-persistence-jsonl/README.zh.md b/packages/session/session-persistence-jsonl/README.zh.md similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/README.zh.md rename to packages/session/session-persistence-jsonl/README.zh.md diff --git a/packages/session-persistence/session-persistence-jsonl/package.json b/packages/session/session-persistence-jsonl/package.json similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/package.json rename to packages/session/session-persistence-jsonl/package.json diff --git a/packages/session-persistence/session-persistence-jsonl/src/format.ts b/packages/session/session-persistence-jsonl/src/format.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/format.ts rename to packages/session/session-persistence-jsonl/src/format.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/index.ts b/packages/session/session-persistence-jsonl/src/index.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/index.ts rename to packages/session/session-persistence-jsonl/src/index.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/invariant.ts b/packages/session/session-persistence-jsonl/src/invariant.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/invariant.ts rename to packages/session/session-persistence-jsonl/src/invariant.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/win32.ts b/packages/session/session-persistence-jsonl/src/win32.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/win32.ts rename to packages/session/session-persistence-jsonl/src/win32.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/zstd-private-decoder.ts b/packages/session/session-persistence-jsonl/src/zstd-private-decoder.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/zstd-private-decoder.ts rename to packages/session/session-persistence-jsonl/src/zstd-private-decoder.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/zstd-public-decoder.ts b/packages/session/session-persistence-jsonl/src/zstd-public-decoder.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/zstd-public-decoder.ts rename to packages/session/session-persistence-jsonl/src/zstd-public-decoder.ts diff --git a/packages/session-persistence/session-persistence-jsonl/src/zstd.ts b/packages/session/session-persistence-jsonl/src/zstd.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/src/zstd.ts rename to packages/session/session-persistence-jsonl/src/zstd.ts diff --git a/packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts b/packages/session/session-persistence-jsonl/tests/jsonl.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/tests/jsonl.spec.ts rename to packages/session/session-persistence-jsonl/tests/jsonl.spec.ts diff --git a/packages/session-persistence/session-persistence-jsonl/tests/win32.spec.ts b/packages/session/session-persistence-jsonl/tests/win32.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/tests/win32.spec.ts rename to packages/session/session-persistence-jsonl/tests/win32.spec.ts diff --git a/packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts b/packages/session/session-persistence-jsonl/tests/zstd.compat.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts rename to packages/session/session-persistence-jsonl/tests/zstd.compat.spec.ts diff --git a/packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts b/packages/session/session-persistence-jsonl/tests/zstd.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence-jsonl/tests/zstd.spec.ts rename to packages/session/session-persistence-jsonl/tests/zstd.spec.ts diff --git a/packages/session-persistence/session-persistence-jsonl/tsconfig.json b/packages/session/session-persistence-jsonl/tsconfig.json similarity index 87% rename from packages/session-persistence/session-persistence-jsonl/tsconfig.json rename to packages/session/session-persistence-jsonl/tsconfig.json index 044156938b..240c0b0ea9 100644 --- a/packages/session-persistence/session-persistence-jsonl/tsconfig.json +++ b/packages/session/session-persistence-jsonl/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/session-persistence/session-persistence-sqlite/README.i18n.yaml b/packages/session/session-persistence-sqlite/README.i18n.yaml similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/README.i18n.yaml rename to packages/session/session-persistence-sqlite/README.i18n.yaml diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session/session-persistence-sqlite/README.md similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/README.md rename to packages/session/session-persistence-sqlite/README.md diff --git a/packages/session-persistence/session-persistence-sqlite/README.zh.md b/packages/session/session-persistence-sqlite/README.zh.md similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/README.zh.md rename to packages/session/session-persistence-sqlite/README.zh.md diff --git a/packages/session-persistence/session-persistence-sqlite/package.json b/packages/session/session-persistence-sqlite/package.json similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/package.json rename to packages/session/session-persistence-sqlite/package.json diff --git a/packages/session-persistence/session-persistence-sqlite/src/index.ts b/packages/session/session-persistence-sqlite/src/index.ts similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/src/index.ts rename to packages/session/session-persistence-sqlite/src/index.ts diff --git a/packages/session-persistence/session-persistence-sqlite/src/invariant.ts b/packages/session/session-persistence-sqlite/src/invariant.ts similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/src/invariant.ts rename to packages/session/session-persistence-sqlite/src/invariant.ts diff --git a/packages/session-persistence/session-persistence-sqlite/src/schema.ts b/packages/session/session-persistence-sqlite/src/schema.ts similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/src/schema.ts rename to packages/session/session-persistence-sqlite/src/schema.ts diff --git a/packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts b/packages/session/session-persistence-sqlite/tests/sqlite.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence-sqlite/tests/sqlite.spec.ts rename to packages/session/session-persistence-sqlite/tests/sqlite.spec.ts diff --git a/packages/session-persistence/session-persistence-sqlite/tsconfig.json b/packages/session/session-persistence-sqlite/tsconfig.json similarity index 87% rename from packages/session-persistence/session-persistence-sqlite/tsconfig.json rename to packages/session/session-persistence-sqlite/tsconfig.json index 044156938b..240c0b0ea9 100644 --- a/packages/session-persistence/session-persistence-sqlite/tsconfig.json +++ b/packages/session/session-persistence-sqlite/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/session-persistence/session-persistence/README.i18n.yaml b/packages/session/session-persistence/README.i18n.yaml similarity index 100% rename from packages/session-persistence/session-persistence/README.i18n.yaml rename to packages/session/session-persistence/README.i18n.yaml diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session/session-persistence/README.md similarity index 100% rename from packages/session-persistence/session-persistence/README.md rename to packages/session/session-persistence/README.md diff --git a/packages/session-persistence/session-persistence/README.zh.md b/packages/session/session-persistence/README.zh.md similarity index 100% rename from packages/session-persistence/session-persistence/README.zh.md rename to packages/session/session-persistence/README.zh.md diff --git a/packages/session-persistence/session-persistence/package.json b/packages/session/session-persistence/package.json similarity index 100% rename from packages/session-persistence/session-persistence/package.json rename to packages/session/session-persistence/package.json diff --git a/packages/session-persistence/session-persistence/src/coordinator.ts b/packages/session/session-persistence/src/coordinator.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/coordinator.ts rename to packages/session/session-persistence/src/coordinator.ts diff --git a/packages/session-persistence/session-persistence/src/index.ts b/packages/session/session-persistence/src/index.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/index.ts rename to packages/session/session-persistence/src/index.ts diff --git a/packages/session-persistence/session-persistence/src/invariant.ts b/packages/session/session-persistence/src/invariant.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/invariant.ts rename to packages/session/session-persistence/src/invariant.ts diff --git a/packages/session-persistence/session-persistence/src/preparations.ts b/packages/session/session-persistence/src/preparations.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/preparations.ts rename to packages/session/session-persistence/src/preparations.ts diff --git a/packages/session-persistence/session-persistence/src/revision.ts b/packages/session/session-persistence/src/revision.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/revision.ts rename to packages/session/session-persistence/src/revision.ts diff --git a/packages/session-persistence/session-persistence/src/write-behind.ts b/packages/session/session-persistence/src/write-behind.ts similarity index 100% rename from packages/session-persistence/session-persistence/src/write-behind.ts rename to packages/session/session-persistence/src/write-behind.ts diff --git a/packages/session-persistence/session-persistence/tests/contract.ts b/packages/session/session-persistence/tests/contract.ts similarity index 100% rename from packages/session-persistence/session-persistence/tests/contract.ts rename to packages/session/session-persistence/tests/contract.ts diff --git a/packages/session-persistence/session-persistence/tests/coordinator-contract.ts b/packages/session/session-persistence/tests/coordinator-contract.ts similarity index 100% rename from packages/session-persistence/session-persistence/tests/coordinator-contract.ts rename to packages/session/session-persistence/tests/coordinator-contract.ts diff --git a/packages/session-persistence/session-persistence/tests/persistence.spec.ts b/packages/session/session-persistence/tests/persistence.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence/tests/persistence.spec.ts rename to packages/session/session-persistence/tests/persistence.spec.ts diff --git a/packages/session-persistence/session-persistence/tests/preparations.spec.ts b/packages/session/session-persistence/tests/preparations.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence/tests/preparations.spec.ts rename to packages/session/session-persistence/tests/preparations.spec.ts diff --git a/packages/session-persistence/session-persistence/tests/write-behind.spec.ts b/packages/session/session-persistence/tests/write-behind.spec.ts similarity index 100% rename from packages/session-persistence/session-persistence/tests/write-behind.spec.ts rename to packages/session/session-persistence/tests/write-behind.spec.ts diff --git a/packages/session-persistence/session-persistence/tsconfig.json b/packages/session/session-persistence/tsconfig.json similarity index 100% rename from packages/session-persistence/session-persistence/tsconfig.json rename to packages/session/session-persistence/tsconfig.json diff --git a/packages/session-projection/session-projection-cache/README.i18n.yaml b/packages/session/session-projection-cache/README.i18n.yaml similarity index 100% rename from packages/session-projection/session-projection-cache/README.i18n.yaml rename to packages/session/session-projection-cache/README.i18n.yaml diff --git a/packages/session-projection/session-projection-cache/README.md b/packages/session/session-projection-cache/README.md similarity index 100% rename from packages/session-projection/session-projection-cache/README.md rename to packages/session/session-projection-cache/README.md diff --git a/packages/session-projection/session-projection-cache/README.zh.md b/packages/session/session-projection-cache/README.zh.md similarity index 100% rename from packages/session-projection/session-projection-cache/README.zh.md rename to packages/session/session-projection-cache/README.zh.md diff --git a/packages/session-projection/session-projection-cache/package.json b/packages/session/session-projection-cache/package.json similarity index 100% rename from packages/session-projection/session-projection-cache/package.json rename to packages/session/session-projection-cache/package.json diff --git a/packages/session-projection/session-projection-cache/src/index.ts b/packages/session/session-projection-cache/src/index.ts similarity index 100% rename from packages/session-projection/session-projection-cache/src/index.ts rename to packages/session/session-projection-cache/src/index.ts diff --git a/packages/session-projection/session-projection-cache/src/invariant.ts b/packages/session/session-projection-cache/src/invariant.ts similarity index 100% rename from packages/session-projection/session-projection-cache/src/invariant.ts rename to packages/session/session-projection-cache/src/invariant.ts diff --git a/packages/session-projection/session-projection-cache/src/spec.ts b/packages/session/session-projection-cache/src/spec.ts similarity index 100% rename from packages/session-projection/session-projection-cache/src/spec.ts rename to packages/session/session-projection-cache/src/spec.ts diff --git a/packages/session-projection/session-projection-cache/tests/cache.spec.ts b/packages/session/session-projection-cache/tests/cache.spec.ts similarity index 100% rename from packages/session-projection/session-projection-cache/tests/cache.spec.ts rename to packages/session/session-projection-cache/tests/cache.spec.ts diff --git a/packages/session-projection/session-projection-cache/tsconfig.json b/packages/session/session-projection-cache/tsconfig.json similarity index 90% rename from packages/session-projection/session-projection-cache/tsconfig.json rename to packages/session/session-projection-cache/tsconfig.json index baba6c805b..cf253dbe79 100644 --- a/packages/session-projection/session-projection-cache/tsconfig.json +++ b/packages/session/session-projection-cache/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../session-persistence" }, { "path": "../session-projection" diff --git a/packages/session-projection/session-projection/README.i18n.yaml b/packages/session/session-projection/README.i18n.yaml similarity index 100% rename from packages/session-projection/session-projection/README.i18n.yaml rename to packages/session/session-projection/README.i18n.yaml diff --git a/packages/session-projection/session-projection/README.md b/packages/session/session-projection/README.md similarity index 100% rename from packages/session-projection/session-projection/README.md rename to packages/session/session-projection/README.md diff --git a/packages/session-projection/session-projection/README.zh.md b/packages/session/session-projection/README.zh.md similarity index 100% rename from packages/session-projection/session-projection/README.zh.md rename to packages/session/session-projection/README.zh.md diff --git a/packages/session-projection/session-projection/package.json b/packages/session/session-projection/package.json similarity index 100% rename from packages/session-projection/session-projection/package.json rename to packages/session/session-projection/package.json diff --git a/packages/session-projection/session-projection/src/index.ts b/packages/session/session-projection/src/index.ts similarity index 100% rename from packages/session-projection/session-projection/src/index.ts rename to packages/session/session-projection/src/index.ts diff --git a/packages/session-projection/session-projection/src/invariant.ts b/packages/session/session-projection/src/invariant.ts similarity index 100% rename from packages/session-projection/session-projection/src/invariant.ts rename to packages/session/session-projection/src/invariant.ts diff --git a/packages/session-projection/session-projection/src/types.ts b/packages/session/session-projection/src/types.ts similarity index 100% rename from packages/session-projection/session-projection/src/types.ts rename to packages/session/session-projection/src/types.ts diff --git a/packages/session-projection/session-projection/tests/registry.spec.ts b/packages/session/session-projection/tests/registry.spec.ts similarity index 100% rename from packages/session-projection/session-projection/tests/registry.spec.ts rename to packages/session/session-projection/tests/registry.spec.ts diff --git a/packages/session-projection/session-projection/tsconfig.json b/packages/session/session-projection/tsconfig.json similarity index 100% rename from packages/session-projection/session-projection/tsconfig.json rename to packages/session/session-projection/tsconfig.json diff --git a/packages/telemetry/session-telemetry-otel/README.i18n.yaml b/packages/session/session-telemetry-otel/README.i18n.yaml similarity index 100% rename from packages/telemetry/session-telemetry-otel/README.i18n.yaml rename to packages/session/session-telemetry-otel/README.i18n.yaml diff --git a/packages/telemetry/session-telemetry-otel/README.md b/packages/session/session-telemetry-otel/README.md similarity index 100% rename from packages/telemetry/session-telemetry-otel/README.md rename to packages/session/session-telemetry-otel/README.md diff --git a/packages/telemetry/session-telemetry-otel/README.zh.md b/packages/session/session-telemetry-otel/README.zh.md similarity index 100% rename from packages/telemetry/session-telemetry-otel/README.zh.md rename to packages/session/session-telemetry-otel/README.zh.md diff --git a/packages/telemetry/session-telemetry-otel/package.json b/packages/session/session-telemetry-otel/package.json similarity index 100% rename from packages/telemetry/session-telemetry-otel/package.json rename to packages/session/session-telemetry-otel/package.json diff --git a/packages/telemetry/session-telemetry-otel/src/index.ts b/packages/session/session-telemetry-otel/src/index.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/src/index.ts rename to packages/session/session-telemetry-otel/src/index.ts diff --git a/packages/telemetry/session-telemetry-otel/src/invariant.ts b/packages/session/session-telemetry-otel/src/invariant.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/src/invariant.ts rename to packages/session/session-telemetry-otel/src/invariant.ts diff --git a/packages/telemetry/session-telemetry-otel/src/user-id.ts b/packages/session/session-telemetry-otel/src/user-id.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/src/user-id.ts rename to packages/session/session-telemetry-otel/src/user-id.ts diff --git a/packages/telemetry/session-telemetry-otel/tests/loader-composition.e2e.ts b/packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/tests/loader-composition.e2e.ts rename to packages/session/session-telemetry-otel/tests/loader-composition.e2e.ts diff --git a/packages/telemetry/session-telemetry-otel/tests/otel.spec.ts b/packages/session/session-telemetry-otel/tests/otel.spec.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/tests/otel.spec.ts rename to packages/session/session-telemetry-otel/tests/otel.spec.ts diff --git a/packages/telemetry/session-telemetry-otel/tests/user-id.spec.ts b/packages/session/session-telemetry-otel/tests/user-id.spec.ts similarity index 100% rename from packages/telemetry/session-telemetry-otel/tests/user-id.spec.ts rename to packages/session/session-telemetry-otel/tests/user-id.spec.ts diff --git a/packages/telemetry/session-telemetry-otel/tsconfig.json b/packages/session/session-telemetry-otel/tsconfig.json similarity index 100% rename from packages/telemetry/session-telemetry-otel/tsconfig.json rename to packages/session/session-telemetry-otel/tsconfig.json diff --git a/packages/telemetry/session-telemetry/README.i18n.yaml b/packages/session/session-telemetry/README.i18n.yaml similarity index 100% rename from packages/telemetry/session-telemetry/README.i18n.yaml rename to packages/session/session-telemetry/README.i18n.yaml diff --git a/packages/telemetry/session-telemetry/README.md b/packages/session/session-telemetry/README.md similarity index 100% rename from packages/telemetry/session-telemetry/README.md rename to packages/session/session-telemetry/README.md diff --git a/packages/telemetry/session-telemetry/README.zh.md b/packages/session/session-telemetry/README.zh.md similarity index 100% rename from packages/telemetry/session-telemetry/README.zh.md rename to packages/session/session-telemetry/README.zh.md diff --git a/packages/telemetry/session-telemetry/package.json b/packages/session/session-telemetry/package.json similarity index 100% rename from packages/telemetry/session-telemetry/package.json rename to packages/session/session-telemetry/package.json diff --git a/packages/telemetry/session-telemetry/src/coordinator.ts b/packages/session/session-telemetry/src/coordinator.ts similarity index 100% rename from packages/telemetry/session-telemetry/src/coordinator.ts rename to packages/session/session-telemetry/src/coordinator.ts diff --git a/packages/telemetry/session-telemetry/src/index.ts b/packages/session/session-telemetry/src/index.ts similarity index 100% rename from packages/telemetry/session-telemetry/src/index.ts rename to packages/session/session-telemetry/src/index.ts diff --git a/packages/telemetry/session-telemetry/src/invariant.ts b/packages/session/session-telemetry/src/invariant.ts similarity index 100% rename from packages/telemetry/session-telemetry/src/invariant.ts rename to packages/session/session-telemetry/src/invariant.ts diff --git a/packages/telemetry/session-telemetry/tests/redact.spec.ts b/packages/session/session-telemetry/tests/redact.spec.ts similarity index 100% rename from packages/telemetry/session-telemetry/tests/redact.spec.ts rename to packages/session/session-telemetry/tests/redact.spec.ts diff --git a/packages/telemetry/session-telemetry/tests/telemetry.spec.ts b/packages/session/session-telemetry/tests/telemetry.spec.ts similarity index 100% rename from packages/telemetry/session-telemetry/tests/telemetry.spec.ts rename to packages/session/session-telemetry/tests/telemetry.spec.ts diff --git a/packages/telemetry/session-telemetry/tsconfig.json b/packages/session/session-telemetry/tsconfig.json similarity index 100% rename from packages/telemetry/session-telemetry/tsconfig.json rename to packages/session/session-telemetry/tsconfig.json diff --git a/packages/session-title/session-title-all-messages-llm/README.i18n.yaml b/packages/session/session-title-all-messages-llm/README.i18n.yaml similarity index 100% rename from packages/session-title/session-title-all-messages-llm/README.i18n.yaml rename to packages/session/session-title-all-messages-llm/README.i18n.yaml diff --git a/packages/session-title/session-title-all-messages-llm/README.md b/packages/session/session-title-all-messages-llm/README.md similarity index 100% rename from packages/session-title/session-title-all-messages-llm/README.md rename to packages/session/session-title-all-messages-llm/README.md diff --git a/packages/session-title/session-title-all-messages-llm/README.zh.md b/packages/session/session-title-all-messages-llm/README.zh.md similarity index 100% rename from packages/session-title/session-title-all-messages-llm/README.zh.md rename to packages/session/session-title-all-messages-llm/README.zh.md diff --git a/packages/session-title/session-title-all-messages-llm/package.json b/packages/session/session-title-all-messages-llm/package.json similarity index 100% rename from packages/session-title/session-title-all-messages-llm/package.json rename to packages/session/session-title-all-messages-llm/package.json diff --git a/packages/session-title/session-title-all-messages-llm/src/index.ts b/packages/session/session-title-all-messages-llm/src/index.ts similarity index 100% rename from packages/session-title/session-title-all-messages-llm/src/index.ts rename to packages/session/session-title-all-messages-llm/src/index.ts diff --git a/packages/session-title/session-title-all-messages-llm/src/invariant.ts b/packages/session/session-title-all-messages-llm/src/invariant.ts similarity index 100% rename from packages/session-title/session-title-all-messages-llm/src/invariant.ts rename to packages/session/session-title-all-messages-llm/src/invariant.ts diff --git a/packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts b/packages/session/session-title-all-messages-llm/tests/provider.spec.ts similarity index 100% rename from packages/session-title/session-title-all-messages-llm/tests/provider.spec.ts rename to packages/session/session-title-all-messages-llm/tests/provider.spec.ts diff --git a/packages/session-title/session-title-all-messages-llm/tsconfig.json b/packages/session/session-title-all-messages-llm/tsconfig.json similarity index 100% rename from packages/session-title/session-title-all-messages-llm/tsconfig.json rename to packages/session/session-title-all-messages-llm/tsconfig.json diff --git a/packages/session-title/session-title-first-message-llm/README.i18n.yaml b/packages/session/session-title-first-message-llm/README.i18n.yaml similarity index 100% rename from packages/session-title/session-title-first-message-llm/README.i18n.yaml rename to packages/session/session-title-first-message-llm/README.i18n.yaml diff --git a/packages/session-title/session-title-first-message-llm/README.md b/packages/session/session-title-first-message-llm/README.md similarity index 100% rename from packages/session-title/session-title-first-message-llm/README.md rename to packages/session/session-title-first-message-llm/README.md diff --git a/packages/session-title/session-title-first-message-llm/README.zh.md b/packages/session/session-title-first-message-llm/README.zh.md similarity index 100% rename from packages/session-title/session-title-first-message-llm/README.zh.md rename to packages/session/session-title-first-message-llm/README.zh.md diff --git a/packages/session-title/session-title-first-message-llm/package.json b/packages/session/session-title-first-message-llm/package.json similarity index 100% rename from packages/session-title/session-title-first-message-llm/package.json rename to packages/session/session-title-first-message-llm/package.json diff --git a/packages/session-title/session-title-first-message-llm/src/index.ts b/packages/session/session-title-first-message-llm/src/index.ts similarity index 100% rename from packages/session-title/session-title-first-message-llm/src/index.ts rename to packages/session/session-title-first-message-llm/src/index.ts diff --git a/packages/session-title/session-title-first-message-llm/src/invariant.ts b/packages/session/session-title-first-message-llm/src/invariant.ts similarity index 100% rename from packages/session-title/session-title-first-message-llm/src/invariant.ts rename to packages/session/session-title-first-message-llm/src/invariant.ts diff --git a/packages/session-title/session-title-first-message-llm/tests/loader-composition.spec.ts b/packages/session/session-title-first-message-llm/tests/loader-composition.spec.ts similarity index 100% rename from packages/session-title/session-title-first-message-llm/tests/loader-composition.spec.ts rename to packages/session/session-title-first-message-llm/tests/loader-composition.spec.ts diff --git a/packages/session-title/session-title-first-message-llm/tests/provider.e2e.ts b/packages/session/session-title-first-message-llm/tests/provider.e2e.ts similarity index 100% rename from packages/session-title/session-title-first-message-llm/tests/provider.e2e.ts rename to packages/session/session-title-first-message-llm/tests/provider.e2e.ts diff --git a/packages/session-title/session-title-first-message-llm/tests/provider.spec.ts b/packages/session/session-title-first-message-llm/tests/provider.spec.ts similarity index 100% rename from packages/session-title/session-title-first-message-llm/tests/provider.spec.ts rename to packages/session/session-title-first-message-llm/tests/provider.spec.ts diff --git a/packages/session-title/session-title-first-message-llm/tsconfig.json b/packages/session/session-title-first-message-llm/tsconfig.json similarity index 100% rename from packages/session-title/session-title-first-message-llm/tsconfig.json rename to packages/session/session-title-first-message-llm/tsconfig.json diff --git a/packages/session-title/session-title-llm/README.i18n.yaml b/packages/session/session-title-llm/README.i18n.yaml similarity index 100% rename from packages/session-title/session-title-llm/README.i18n.yaml rename to packages/session/session-title-llm/README.i18n.yaml diff --git a/packages/session-title/session-title-llm/README.md b/packages/session/session-title-llm/README.md similarity index 100% rename from packages/session-title/session-title-llm/README.md rename to packages/session/session-title-llm/README.md diff --git a/packages/session-title/session-title-llm/README.zh.md b/packages/session/session-title-llm/README.zh.md similarity index 100% rename from packages/session-title/session-title-llm/README.zh.md rename to packages/session/session-title-llm/README.zh.md diff --git a/packages/session-title/session-title-llm/package.json b/packages/session/session-title-llm/package.json similarity index 100% rename from packages/session-title/session-title-llm/package.json rename to packages/session/session-title-llm/package.json diff --git a/packages/session-title/session-title-llm/src/index.ts b/packages/session/session-title-llm/src/index.ts similarity index 100% rename from packages/session-title/session-title-llm/src/index.ts rename to packages/session/session-title-llm/src/index.ts diff --git a/packages/session-title/session-title-llm/src/invariant.ts b/packages/session/session-title-llm/src/invariant.ts similarity index 100% rename from packages/session-title/session-title-llm/src/invariant.ts rename to packages/session/session-title-llm/src/invariant.ts diff --git a/packages/session-title/session-title-llm/tests/llm.spec.ts b/packages/session/session-title-llm/tests/llm.spec.ts similarity index 100% rename from packages/session-title/session-title-llm/tests/llm.spec.ts rename to packages/session/session-title-llm/tests/llm.spec.ts diff --git a/packages/session-title/session-title-llm/tsconfig.json b/packages/session/session-title-llm/tsconfig.json similarity index 100% rename from packages/session-title/session-title-llm/tsconfig.json rename to packages/session/session-title-llm/tsconfig.json diff --git a/packages/session-title/session-title/README.i18n.yaml b/packages/session/session-title/README.i18n.yaml similarity index 100% rename from packages/session-title/session-title/README.i18n.yaml rename to packages/session/session-title/README.i18n.yaml diff --git a/packages/session-title/session-title/README.md b/packages/session/session-title/README.md similarity index 100% rename from packages/session-title/session-title/README.md rename to packages/session/session-title/README.md diff --git a/packages/session-title/session-title/README.zh.md b/packages/session/session-title/README.zh.md similarity index 100% rename from packages/session-title/session-title/README.zh.md rename to packages/session/session-title/README.zh.md diff --git a/packages/session-title/session-title/package.json b/packages/session/session-title/package.json similarity index 100% rename from packages/session-title/session-title/package.json rename to packages/session/session-title/package.json diff --git a/packages/session-title/session-title/src/client.ts b/packages/session/session-title/src/client.ts similarity index 100% rename from packages/session-title/session-title/src/client.ts rename to packages/session/session-title/src/client.ts diff --git a/packages/session-title/session-title/src/index.ts b/packages/session/session-title/src/index.ts similarity index 100% rename from packages/session-title/session-title/src/index.ts rename to packages/session/session-title/src/index.ts diff --git a/packages/session-title/session-title/src/invariant.ts b/packages/session/session-title/src/invariant.ts similarity index 100% rename from packages/session-title/session-title/src/invariant.ts rename to packages/session/session-title/src/invariant.ts diff --git a/packages/session-title/session-title/src/normalize.ts b/packages/session/session-title/src/normalize.ts similarity index 100% rename from packages/session-title/session-title/src/normalize.ts rename to packages/session/session-title/src/normalize.ts diff --git a/packages/session-title/session-title/src/types.ts b/packages/session/session-title/src/types.ts similarity index 100% rename from packages/session-title/session-title/src/types.ts rename to packages/session/session-title/src/types.ts diff --git a/packages/session-title/session-title/tests/invariant.spec.ts b/packages/session/session-title/tests/invariant.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/invariant.spec.ts rename to packages/session/session-title/tests/invariant.spec.ts diff --git a/packages/session-title/session-title/tests/persistence.spec.ts b/packages/session/session-title/tests/persistence.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/persistence.spec.ts rename to packages/session/session-title/tests/persistence.spec.ts diff --git a/packages/session-title/session-title/tests/projection.spec.ts b/packages/session/session-title/tests/projection.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/projection.spec.ts rename to packages/session/session-title/tests/projection.spec.ts diff --git a/packages/session-title/session-title/tests/provider.spec.ts b/packages/session/session-title/tests/provider.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/provider.spec.ts rename to packages/session/session-title/tests/provider.spec.ts diff --git a/packages/session-title/session-title/tests/rename.spec.ts b/packages/session/session-title/tests/rename.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/rename.spec.ts rename to packages/session/session-title/tests/rename.spec.ts diff --git a/packages/session-title/session-title/tests/service-contracts.spec.ts b/packages/session/session-title/tests/service-contracts.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/service-contracts.spec.ts rename to packages/session/session-title/tests/service-contracts.spec.ts diff --git a/packages/session-title/session-title/tests/session-title.spec.ts b/packages/session/session-title/tests/session-title.spec.ts similarity index 100% rename from packages/session-title/session-title/tests/session-title.spec.ts rename to packages/session/session-title/tests/session-title.spec.ts diff --git a/packages/session-title/session-title/tsconfig.json b/packages/session/session-title/tsconfig.json similarity index 90% rename from packages/session-title/session-title/tsconfig.json rename to packages/session/session-title/tsconfig.json index 80aef8bbfb..ca7c8e65dd 100644 --- a/packages/session-title/session-title/tsconfig.json +++ b/packages/session/session-title/tsconfig.json @@ -30,7 +30,7 @@ "path": "../../core/session" }, { - "path": "../../session-projection/session-projection" + "path": "../session-projection" } ] } diff --git a/packages/subagent/subagent/tsconfig.json b/packages/subagent/subagent/tsconfig.json index de2fff3d84..ccfecc02e4 100644 --- a/packages/subagent/subagent/tsconfig.json +++ b/packages/subagent/subagent/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../core/scope" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../session-projection/session-projection" diff --git a/packages/telemetry/README.i18n.yaml b/packages/telemetry/README.i18n.yaml deleted file mode 100644 index 6f60d47612..0000000000 --- a/packages/telemetry/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/telemetry/README.md -README.md: d1910323176738d1ecab8fe8e6c07a2811f0f5cd -README.zh.md: 229cd3acc5ce3dc49a9c862efa824f4ee6a1fa7b diff --git a/packages/telemetry/README.md b/packages/telemetry/README.md deleted file mode 100644 index d191032317..0000000000 --- a/packages/telemetry/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# telemetry/ — session telemetry capability family - -English | [中文](README.zh.md) - -This family projects session activity into outbound telemetry and delegates delivery to a configured reporting backend. The [telemetry decision](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md) records the reporting boundary; the [mode decision](../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.md) records immediate, feedback-gated, and disabled delivery. - -| Package | Role | -|---|---| -| [`session-telemetry/`](session-telemetry/README.md) | Defines capture, redaction, projection, and live or on-demand backend delivery. | -| [`session-telemetry-otel/`](session-telemetry-otel/README.md) | Delivers telemetry through OpenTelemetry logs in `FULL`, `FEEDBACK_ONLY`, or `DISABLED` mode. | diff --git a/packages/telemetry/README.zh.md b/packages/telemetry/README.zh.md deleted file mode 100644 index 229cd3acc5..0000000000 --- a/packages/telemetry/README.zh.md +++ /dev/null @@ -1,10 +0,0 @@ -# telemetry/:会话遥测能力家族 - -[English](README.md) | 中文 - -本家族将会话活动投影为外发遥测,并将投递委派给配置的上报后端。[遥测决策](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)记录上报边界;[模式决策](../../.agents/notes/implemented/feature/2026-08-05-feedback-gated-session-telemetry.md)记录即时、反馈门控与禁用投递。 - -| 包 | 职责 | -|---|---| -| [`session-telemetry/`](session-telemetry/README.md) | 定义捕获、脱敏、投影,以及实时或按需后端投递。 | -| [`session-telemetry-otel/`](session-telemetry-otel/README.md) | 通过 OpenTelemetry 日志以 `FULL`、`FEEDBACK_ONLY` 或 `DISABLED` 模式投递遥测。 | diff --git a/packages/todo/tool-todo/README.i18n.yaml b/packages/todo/tool-todo/README.i18n.yaml index 20b8dffbbf..6aa521df26 100644 --- a/packages/todo/tool-todo/README.i18n.yaml +++ b/packages/todo/tool-todo/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/todo/tool-todo/README.md -README.md: 914e89a000e4bb87ebd7844f05db3809c6726528 -README.zh.md: c88dbf976fa5110028fcc964ab9aa8efcc3244d3 +README.md: 7c993ac6f7f0047d008a09e5ff52cb752f94763c +README.zh.md: 9cea66147beddab7d62ed7344cbe866495e28022 diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index 914e89a000..7c993ac6f7 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -30,7 +30,7 @@ The canonical result is `{ todos, counts: { pending, inProgress, completed } }`; ## Session projection -When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `todos` projection unit under an injected child: `init` = `null` (no write yet), `apply` = take the whole list from each `todo/write` and clear to `null` on each `turn/start` (standing plan; `turn/end` keeps the finished checklist; every other event returns the same state reference), `view` = identity, `stateVersion` = 2. The key merges into `SessionProjectionMap` here (via the interface package's `/types` outlet); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected. Lifetime rationale: [todo plan clears on next turn](../../../.agents/notes/implemented/feature/2026-07-28-todo-plan-clears-on-next-turn.md). +When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `todos` projection unit under an injected child: `init` = `null` (no write yet), `apply` = take the whole list from each `todo/write` and clear to `null` on each `turn/start` (standing plan; `turn/end` keeps the finished checklist; every other event returns the same state reference), `view` = identity, `stateVersion` = 2. The key merges into `SessionProjectionMap` here (via the interface package's `/types` outlet); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected. Lifetime rationale: [todo plan clears on next turn](../../../.agents/notes/implemented/feature/2026-07-28-todo-plan-clears-on-next-turn.md). ## Export shape diff --git a/packages/todo/tool-todo/README.zh.md b/packages/todo/tool-todo/README.zh.md index c88dbf976f..9cea66147b 100644 --- a/packages/todo/tool-todo/README.zh.md +++ b/packages/todo/tool-todo/README.zh.md @@ -30,7 +30,7 @@ ## 会话投影 -当组合挂载了 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md))时,本包在一个注入的子插件中注册 `todos` 投影单元:`init` = `null`(尚无写入)、`apply` = 从每个 `todo/write` 取整表,并在每个 `turn/start` 清为 `null`(当前有效计划;`turn/end` 保留刚完成的清单;其余事件都返回同一个状态引用)、`view` = 恒等、`stateVersion` = 2。该键在本包中合并进 `SessionProjectionMap`(经接口包的 `/types` 出口);框架驱动该单元,载体通过历史尾页与 `session/projection` 推送帧提供该值。未挂载注册表的组合不受影响。生命周期理由见 [在下一轮次清空 todo 计划](../../../.agents/notes/implemented/feature/2026-07-28-todo-plan-clears-on-next-turn.md)。 +当组合挂载了 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md))时,本包在一个注入的子插件中注册 `todos` 投影单元:`init` = `null`(尚无写入)、`apply` = 从每个 `todo/write` 取整表,并在每个 `turn/start` 清为 `null`(当前有效计划;`turn/end` 保留刚完成的清单;其余事件都返回同一个状态引用)、`view` = 恒等、`stateVersion` = 2。该键在本包中合并进 `SessionProjectionMap`(经接口包的 `/types` 出口);框架驱动该单元,载体通过历史尾页与 `session/projection` 推送帧提供该值。未挂载注册表的组合不受影响。生命周期理由见 [在下一轮次清空 todo 计划](../../../.agents/notes/implemented/feature/2026-07-28-todo-plan-clears-on-next-turn.md)。 ## 导出形状 diff --git a/packages/todo/tool-todo/tsconfig.json b/packages/todo/tool-todo/tsconfig.json index b35157e58d..6160fdbeb7 100644 --- a/packages/todo/tool-todo/tsconfig.json +++ b/packages/todo/tool-todo/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../core/session" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../support/invariants" diff --git a/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts b/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts index 051b0eef20..1de16660a8 100644 --- a/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts +++ b/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts @@ -35,7 +35,7 @@ const [ load("vendor/cordis/lib/index.js"), load("packages/examples/agent-spine-demo/lib/index.js"), load("packages/subagent/subagent/lib/index.js"), - load("packages/session-persistence/session-persistence-jsonl/lib/index.js"), + load("packages/session/session-persistence-jsonl/lib/index.js"), load("packages/ui/jsonrpc/lib/index.js"), load("packages/core/session/lib/index.js"), ]); diff --git a/packages/ui/permission/tsconfig.json b/packages/ui/permission/tsconfig.json index 9dc4afcd9a..b951d622a8 100644 --- a/packages/ui/permission/tsconfig.json +++ b/packages/ui/permission/tsconfig.json @@ -36,7 +36,7 @@ "path": "../../support/invariants" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { "path": "../../settings/settings" diff --git a/packages/workspace/workspace/tsconfig.json b/packages/workspace/workspace/tsconfig.json index 0bddf4672c..bbdf80fae2 100644 --- a/packages/workspace/workspace/tsconfig.json +++ b/packages/workspace/workspace/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../util/brand" diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index b17cc5b4f8..2c347613e8 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -270,11 +270,11 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { DomainSpec: 'domain declaration contract is owned by packages/storage/storage-domain/README.md', StorageBackend: 'backend contract is owned by packages/storage/storage/src/backend.ts', StorageForms: 'merge-extensible form map is owned by packages/storage/storage/src/index.ts', - ProjectionDefinition: 'projection unit contract is owned by packages/session-projection/session-projection/README.md', - SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session-projection/session-projection/src/types.ts', - ProjectionChangeListener: 'change-feed listener contract is owned by packages/session-projection/session-projection/src/index.ts', - ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session-projection/session-projection/src/index.ts', - ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session-projection/session-projection/src/index.ts', + ProjectionDefinition: 'projection unit contract is owned by packages/session/session-projection/README.md', + SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session/session-projection/src/types.ts', + ProjectionChangeListener: 'change-feed listener contract is owned by packages/session/session-projection/src/index.ts', + ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session/session-projection/src/index.ts', + ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session/session-projection/src/index.ts', CommandExecution: 'executor return contract is owned by packages/ui/commands/src/index.ts', TypertContribution: 'registry contribution contract is owned by packages/typert/registry/README.md', TypertFace: 'registry face identity is owned by packages/typert/registry/README.md', @@ -305,7 +305,7 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts', SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', SubagentRunInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', - TelemetryRecord: 'seam-local record contract is owned by packages/telemetry/session-telemetry/src/index.ts', + TelemetryRecord: 'seam-local record contract is owned by packages/session/session-telemetry/src/index.ts', WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts', WorkflowAgentInfo: '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', diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 74a09a874e..258b70f87d 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -312,7 +312,7 @@ function nodeCompatSmokeGates(options: { cliSmoke?: boolean } = {}): Gate[] { pnpmExec('jsonl-zstd-smoke', [ 'vitest', 'run', - 'packages/session-persistence/session-persistence-jsonl/tests/zstd.compat.spec.ts', + 'packages/session/session-persistence-jsonl/tests/zstd.compat.spec.ts', ], { label: 'JSONL Zstandard smoke' }), pnpmExec('dsh-source-launch-smoke', [ 'vitest', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 9c99ed4632..e4eaf15398 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -448,7 +448,7 @@ { "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionLocation", - "source": "packages/session-persistence/session-persistence/src/index.ts" + "source": "packages/session/session-persistence/src/index.ts" }, { "doc": "docs/core-data-structures/session-query.md", @@ -528,57 +528,57 @@ { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleProviderId", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleModelProvenance", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleSource", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleEventData", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleSnapshot", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleLlmRequestEventData", - "source": "packages/session-title/session-title-llm/src/index.ts" + "source": "packages/session/session-title-llm/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleUserMessage", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleAutomaticMode", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleProviderRequest", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleProviderResult", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-title.md", "symbol": "SessionTitleProvider", - "source": "packages/session-title/session-title/src/index.ts" + "source": "packages/session/session-title/src/index.ts" }, { "doc": "docs/core-data-structures/session-reference.md", @@ -1323,12 +1323,12 @@ { "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionPersistenceRevision", - "source": "packages/session-persistence/session-persistence/src/revision.ts" + "source": "packages/session/session-persistence/src/revision.ts" }, { "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionPersistenceSnapshot", - "source": "packages/session-persistence/session-persistence/src/index.ts" + "source": "packages/session/session-persistence/src/index.ts" }, { "doc": "docs/core-data-structures/session-query.md", diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 6fda3901b6..5930d39e26 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -109,8 +109,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/sdk/sdk-client': { kind: 'none', reason: 'Client-process library; the model surface lives in the spawned runtime\'s composed plugins.' }, 'packages/sdk/sdk-protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model surface.' }, 'packages/sdk/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' }, - 'packages/session-projection/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers no model surface.' }, - 'packages/session-projection/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' }, + 'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers no model surface.' }, + 'packages/session/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' }, 'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' }, 'packages/session-query/session-query-sqlite': { kind: 'none', reason: 'The search backend returns hits only to callers and registers no model surface.' }, 'packages/settings/settings': { kind: 'indirect', reason: 'The seam stores and resolves user settings; consumer plugins own any model surface a value feeds.' }, @@ -118,8 +118,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/credentials/credentials': { kind: 'indirect', reason: 'The seam resolves credential references; the consuming adapter owns every model surface a value authorizes.' }, 'packages/credentials/credentials-local': { kind: 'indirect', reason: 'The file/environment provider stores credential values; consumers of ctx.credentials own any model surface.' }, 'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers no model surface.' }, - 'packages/telemetry/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' }, - 'packages/telemetry/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' }, + 'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' }, + 'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' }, 'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' }, 'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' }, 'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 0673c35b90..e09ab93743 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -54,11 +54,11 @@ "@deepseek-ai/dsh-typert-generator": ["./packages/typert/generator/src/index.ts"], "@deepseek-ai/dsh-session/types": ["./packages/core/session/src/types.ts"], "@deepseek-ai/dsh-session/surface": ["./packages/core/session/src/surface.ts"], - "@deepseek-ai/dsh-session-projection/types": ["./packages/session-projection/session-projection/src/types.ts"], + "@deepseek-ai/dsh-session-projection/types": ["./packages/session/session-projection/src/types.ts"], "@deepseek-ai/dsh-tool-todo/types": ["./packages/todo/tool-todo/src/types.ts"], "@deepseek-ai/dsh-tool-todo/client": ["./packages/todo/tool-todo/src/client.ts"], - "@deepseek-ai/dsh-session-title/types": ["./packages/session-title/session-title/src/types.ts"], - "@deepseek-ai/dsh-session-title/client": ["./packages/session-title/session-title/src/client.ts"], + "@deepseek-ai/dsh-session-title/types": ["./packages/session/session-title/src/types.ts"], + "@deepseek-ai/dsh-session-title/client": ["./packages/session/session-title/src/client.ts"], "@deepseek-ai/dsh-plan-mode/types": ["./packages/plan/plan-mode/src/types.ts"], "@deepseek-ai/dsh-plan-mode/client": ["./packages/plan/plan-mode/src/client.ts"], "@deepseek-ai/dsh-pwsh-local": ["./packages/bash/pwsh-local/src/index.ts"], @@ -106,13 +106,10 @@ "./packages/cordis/*/src/invariant.ts", "./packages/sandbox/*/src/invariant.ts", "./packages/hooks/*/src/invariant.ts", - "./packages/session-persistence/*/src/invariant.ts", - "./packages/session-projection/*/src/invariant.ts", - "./packages/session-title/*/src/invariant.ts", + "./packages/session/*/src/invariant.ts", "./packages/session-query/*/src/invariant.ts", "./packages/settings/*/src/invariant.ts", "./packages/credentials/*/src/invariant.ts", - "./packages/telemetry/*/src/invariant.ts", "./packages/acp/*/src/invariant.ts", "./packages/storage/*/src/invariant.ts", "./packages/workspace/*/src/invariant.ts", @@ -211,13 +208,10 @@ "./packages/cordis/*/src", "./packages/sandbox/*/src", "./packages/hooks/*/src", - "./packages/session-persistence/*/src", - "./packages/session-projection/*/src", + "./packages/session/*/src", "./packages/session-query/*/src", - "./packages/session-title/*/src", "./packages/settings/*/src", "./packages/credentials/*/src", - "./packages/telemetry/*/src", "./packages/acp/*/src", "./packages/storage/*/src", "./packages/workspace/*/src", diff --git a/tsconfig.host.json b/tsconfig.host.json index 101c810c91..0f44c4b7de 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -112,12 +112,12 @@ { "path": "./packages/api/gateway" }, { "path": "./packages/api/remotes/tsconfig.host.json" }, { "path": "./packages/typert/loader" }, - { "path": "./packages/session-persistence/session-persistence" }, - { "path": "./packages/session-persistence/session-checkpoint-policy" }, - { "path": "./packages/session-persistence/session-persistence-jsonl" }, - { "path": "./packages/session-persistence/session-persistence-sqlite" }, - { "path": "./packages/session-projection/session-projection" }, - { "path": "./packages/session-projection/session-projection-cache" }, + { "path": "./packages/session/session-persistence" }, + { "path": "./packages/session/session-checkpoint-policy" }, + { "path": "./packages/session/session-persistence-jsonl" }, + { "path": "./packages/session/session-persistence-sqlite" }, + { "path": "./packages/session/session-projection" }, + { "path": "./packages/session/session-projection-cache" }, { "path": "./packages/session-query/session-query" }, { "path": "./packages/session-query/session-query-sqlite" }, { "path": "./packages/settings/settings" }, @@ -130,12 +130,12 @@ { "path": "./packages/storage/storage-sqlite" }, { "path": "./packages/storage/storage-domain" }, { "path": "./packages/workspace/workspace" }, - { "path": "./packages/session-title/session-title" }, - { "path": "./packages/session-title/session-title-llm" }, - { "path": "./packages/session-title/session-title-first-message-llm" }, - { "path": "./packages/session-title/session-title-all-messages-llm" }, - { "path": "./packages/telemetry/session-telemetry" }, - { "path": "./packages/telemetry/session-telemetry-otel" }, + { "path": "./packages/session/session-title" }, + { "path": "./packages/session/session-title-llm" }, + { "path": "./packages/session/session-title-first-message-llm" }, + { "path": "./packages/session/session-title-all-messages-llm" }, + { "path": "./packages/session/session-telemetry" }, + { "path": "./packages/session/session-telemetry-otel" }, { "path": "./packages/core/system-prompt" }, { "path": "./packages/core/agent" }, { "path": "./packages/ui/commands" }, diff --git a/vitest.config.ts b/vitest.config.ts index 6eb1bda354..29ad406e75 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -224,7 +224,7 @@ export default defineConfig({ // cover and remove with the client test lane above. 'packages/ui/commands/src/index.ts', 'packages/ui/commands/src/invariant.ts', - 'packages/session-projection/session-projection/src/index.ts', + 'packages/session/session-projection/src/index.ts', ...windowsUnsupportedPackages.map(path => `${path}/src/**/*.ts`), ...windowsCoverageExclusions, ...pwshCoverageExclusions, From 3fc35c91ff490e3ad1ad5fed34aa1e3ed7145eec Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 03:13:49 +0800 Subject: [PATCH 03/19] refactor(packages): dissolve ui/ and rename sdk/ to scaffold/ git mv per the regrouping RFC: the five human-collaboration seams and tui join packages/interaction/, app-boot becomes packages/boot/, and jsonrpc joins the renamed scaffold/ (formerly sdk/) as its server half beside client/protocol/create-sdk/helper/scripts/telemetry, whose folders drop the legacy sdk- prefix. Three new group README triplets replace the ui/ and sdk/ ones; tsconfig references/paths/globs, knip keys, vitest globs, gate scripts, catalogs, docs, and the lockfile follow. Adds the four settled FIXME rename markers (dsh-sdk-server, dsh-sdk-telemetry, dsh-sdk-helper, dsh-sdk-scripts). The scaffold folders diverge from their npm names until those renames land, so tsconfig.base.json maps the three affected names explicitly beside the group wildcard. Also repairs two pre-existing stale-path classes the strengthened sweep surfaced: docs/web-styling.md's retired web-ui host package and type-model spec fixture-literal joins. app-boot's three Loader-composition specs time out at the default 5s under full-suite parallel load on this filesystem (pre-existing; pass isolated with --testTimeout=30000); interaction/scaffold/boot suites otherwise green (687 passed). --- ...026-07-27-tui-chat-channel-module-split.md | 2 +- ...-07-27-tui-chat-channel-module-split.zh.md | 2 +- ...-07-24-tui-turn-end-stop-reason-notices.md | 2 +- ...-24-tui-turn-end-stop-reason-notices.zh.md | 2 +- ...17-dedicated-full-screen-tui-front-door.md | 2 +- ...dedicated-full-screen-tui-front-door.zh.md | 2 +- .../feature/2026-07-20-windows-tui-support.md | 2 +- .../2026-07-20-windows-tui-support.zh.md | 2 +- .../2026-07-21-tui-skill-slash-command.md | 2 +- .../2026-07-21-tui-skill-slash-command.zh.md | 2 +- ...-07-27-assistant-timing-header-trailing.md | 2 +- ...-27-assistant-timing-header-trailing.zh.md | 2 +- ...7-26-execa-for-test-subprocess-plumbing.md | 2 +- ...6-execa-for-test-subprocess-plumbing.zh.md | 2 +- ...cutable-sdk-runtime-distribution.i18n.yaml | 4 +-- ...ile-executable-sdk-runtime-distribution.md | 4 +-- ...-executable-sdk-runtime-distribution.zh.md | 4 +-- ...-20-config-hot-reload-resilience.i18n.yaml | 4 +-- ...2026-07-20-config-hot-reload-resilience.md | 2 +- ...6-07-20-config-hot-reload-resilience.zh.md | 2 +- .../2026-07-06-approval-seam.i18n.yaml | 4 +-- .../feature/2026-07-06-approval-seam.md | 4 +-- .../feature/2026-07-06-approval-seam.zh.md | 4 +-- ...nt-persona-tool-filter-and-depth.i18n.yaml | 4 +-- ...-subagent-persona-tool-filter-and-depth.md | 2 +- ...bagent-persona-tool-filter-and-depth.zh.md | 2 +- .../2026-07-16-harness-level-loop.i18n.yaml | 4 +-- .../feature/2026-07-16-harness-level-loop.md | 2 +- .../2026-07-16-harness-level-loop.zh.md | 2 +- ...7-19-plugin-command-registration.i18n.yaml | 4 +-- .../2026-07-19-plugin-command-registration.md | 2 +- ...26-07-19-plugin-command-registration.zh.md | 2 +- ...26-07-20-dsh-cli-personal-config.i18n.yaml | 4 +-- .../2026-07-20-dsh-cli-personal-config.md | 2 +- .../2026-07-20-dsh-cli-personal-config.zh.md | 2 +- ...continuable-background-subagents.i18n.yaml | 4 +-- ...-07-21-continuable-background-subagents.md | 2 +- ...-21-continuable-background-subagents.zh.md | 2 +- ...ipt-sdk-and-sdk-subagent-backend.i18n.yaml | 4 +-- ...typescript-sdk-and-sdk-subagent-backend.md | 4 +-- ...escript-sdk-and-sdk-subagent-backend.zh.md | 4 +-- ...ssion-projection-and-command-log.i18n.yaml | 4 +-- ...7-27-session-projection-and-command-log.md | 2 +- ...7-session-projection-and-command-log.zh.md | 2 +- ...6-07-19-make-jsonrpc-directional.i18n.yaml | 4 +-- .../2026-07-19-make-jsonrpc-directional.md | 6 ++-- .../2026-07-19-make-jsonrpc-directional.zh.md | 6 ++-- AGENTS.md | 4 ++- apps/cli/tsconfig.json | 2 +- docs/architecture.i18n.yaml | 4 +-- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- docs/capability-seams.md | 8 ++--- docs/core-data-structures/approval.i18n.yaml | 4 +-- docs/core-data-structures/approval.md | 4 +-- docs/core-data-structures/approval.zh.md | 4 +-- docs/core-data-structures/commands.i18n.yaml | 4 +-- docs/core-data-structures/commands.md | 4 +-- docs/core-data-structures/commands.zh.md | 4 +-- .../user-interaction.i18n.yaml | 4 +-- docs/core-data-structures/user-interaction.md | 4 +-- .../user-interaction.zh.md | 4 +-- docs/testing.i18n.yaml | 4 +-- docs/testing.md | 2 +- docs/testing.zh.md | 2 +- docs/tool-catalog.md | 2 +- knip.json | 8 ++--- packages/README.i18n.yaml | 4 +-- packages/README.md | 5 +-- packages/README.zh.md | 5 +-- packages/acp/acp/tsconfig.json | 2 +- packages/bash/tool-bash/tsconfig.json | 2 +- packages/{sdk => boot}/README.i18n.yaml | 6 ++-- packages/boot/README.md | 9 +++++ packages/boot/README.zh.md | 9 +++++ .../{ui => boot}/app-boot/README.i18n.yaml | 2 +- packages/{ui => boot}/app-boot/README.md | 0 packages/{ui => boot}/app-boot/README.zh.md | 0 packages/{ui => boot}/app-boot/package.json | 0 packages/{ui => boot}/app-boot/src/index.ts | 0 .../{ui => boot}/app-boot/src/invariant.ts | 0 packages/{ui => boot}/app-boot/src/profile.ts | 0 .../app-boot/tests/app-boot.spec.ts | 0 .../app-boot/tests/config-dump.spec.ts | 0 .../app-boot/tests/config-reload.spec.ts | 0 .../app-boot/tests/hmr-config.spec.ts | 0 .../app-boot/tests/profile.spec.ts | 0 .../app-boot/tests/repository-cache.spec.ts | 0 .../app-boot/tests/user-patches.spec.ts | 0 packages/{ui => boot}/app-boot/tsconfig.json | 0 .../{ui => boot}/app-boot/tsdown.config.ts | 0 packages/bundle/README.i18n.yaml | 6 ++++ packages/client/connection/tsconfig.json | 6 ++-- packages/client/runtime/tsconfig.json | 2 +- packages/client/ui-conversation/tsconfig.json | 2 +- packages/client/ui-permission/tsconfig.json | 2 +- packages/client/ui-question/tsconfig.json | 2 +- .../compact/command-compact/tsconfig.json | 2 +- packages/compact/compact/README.i18n.yaml | 4 +-- packages/compact/compact/README.md | 2 +- packages/compact/compact/README.zh.md | 2 +- packages/core/tools/README.i18n.yaml | 4 +-- packages/core/tools/README.md | 2 +- packages/core/tools/README.zh.md | 2 +- packages/core/tools/tsconfig.json | 2 +- .../examples/acp-demo/tests/built-bin.e2e.ts | 2 +- packages/examples/acp-demo/tsconfig.json | 2 +- packages/examples/jsonrpc-demo/tsconfig.json | 2 +- packages/fs/tool-fs/tsconfig.json | 2 +- packages/goal/command-goal/tsconfig.json | 2 +- packages/host/apiproxy/tsconfig.json | 6 ++-- packages/interaction/README.i18n.yaml | 6 ++++ packages/{ui => interaction}/README.md | 6 ++-- packages/{ui => interaction}/README.zh.md | 6 ++-- .../commands/README.i18n.yaml | 2 +- .../{ui => interaction}/commands/README.md | 0 .../{ui => interaction}/commands/README.zh.md | 0 .../{ui => interaction}/commands/package.json | 0 .../{ui => interaction}/commands/src/brand.ts | 0 .../{ui => interaction}/commands/src/index.ts | 0 .../commands/src/invariant.ts | 0 .../commands/tests/commands.spec.ts | 0 .../commands/tests/invariant.spec.ts | 0 .../commands/tsconfig.json | 0 .../interaction/permission/README.i18n.yaml | 6 ++++ .../{ui => interaction}/permission/README.md | 0 .../permission/README.zh.md | 0 .../permission/package.json | 0 .../permission/src/client.ts | 0 .../permission/src/index.ts | 0 .../permission/src/invariant.ts | 0 .../permission/src/types.ts | 0 .../permission/tests/invariant.spec.ts | 0 .../permission/tests/permission.spec.ts | 0 .../permission/tests/projection.spec.ts | 0 .../permission/tsconfig.json | 0 .../tool-ask-user/README.i18n.yaml | 2 +- .../tool-ask-user/README.md | 0 .../tool-ask-user/README.zh.md | 0 .../tool-ask-user/package.json | 0 .../tool-ask-user/src/index.ts | 0 .../tool-ask-user/src/invariant.ts | 0 .../tool-ask-user/tests/tool-ask-user.spec.ts | 0 .../tool-ask-user/tsconfig.json | 0 .../user-approval/README.i18n.yaml | 2 +- .../user-approval/README.md | 0 .../user-approval/README.zh.md | 0 .../user-approval/package.json | 0 .../user-approval/src/index.ts | 0 .../user-approval/src/invariant.ts | 0 .../user-approval/src/types.ts | 0 .../user-approval/tests/approval.spec.ts | 0 .../user-approval/tests/invariant.spec.ts | 0 .../user-approval/tsconfig.json | 0 .../user-approval/tsdown.config.ts | 0 .../user-interaction/README.i18n.yaml | 6 ++++ .../user-interaction/README.md | 0 .../user-interaction/README.zh.md | 0 .../user-interaction/package.json | 0 .../user-interaction/src/index.ts | 0 .../user-interaction/src/invariant.ts | 0 .../user-interaction/src/types.ts | 0 .../tests/user-interaction.spec.ts | 0 .../user-interaction/tsconfig.json | 0 packages/plan/plan-mode/tsconfig.json | 4 +-- packages/{ui => scaffold}/README.i18n.yaml | 6 ++-- packages/{sdk => scaffold}/README.md | 9 ++--- packages/scaffold/README.zh.md | 17 +++++++++ .../client}/README.i18n.yaml | 2 +- .../sdk-client => scaffold/client}/README.md | 0 .../client}/README.zh.md | 0 .../client}/package.json | 0 .../sdk-client => scaffold/client}/src/api.ts | 0 .../client}/src/client.ts | 0 .../client}/src/dispose.ts | 0 .../client}/src/index.ts | 0 .../client}/src/invariant.ts | 0 .../client}/src/types.ts | 0 .../client}/tests/dispose.spec.ts | 0 .../client}/tests/fake-runtime.ts | 0 .../client}/tests/sdk-client.spec.ts | 0 .../client}/tsconfig.json | 2 +- .../create-sdk/README.i18n.yaml | 2 +- .../{sdk => scaffold}/create-sdk/README.md | 0 .../{sdk => scaffold}/create-sdk/README.zh.md | 0 .../{sdk => scaffold}/create-sdk/package.json | 0 .../{sdk => scaffold}/create-sdk/src/args.ts | 0 .../{sdk => scaffold}/create-sdk/src/bin.ts | 0 .../create-sdk/src/command.ts | 0 .../create-sdk/src/create-questions.ts | 0 .../create-sdk/src/create-wizard.ts | 0 .../create-sdk/src/headless.ts | 0 .../{sdk => scaffold}/create-sdk/src/index.ts | 0 .../create-sdk/src/invariant.ts | 0 .../create-sdk/src/project-scaffolder.ts | 0 .../src/templates/assets/created.txt.tpl | 0 .../templates/assets/install-question.txt.tpl | 0 .../src/templates/assets/next-steps.txt.tpl | 0 .../templates/assets/setup-failure.txt.tpl | 0 .../src/templates/assets/usage.txt.tpl | 0 .../src/templates/create-templates.ts | 0 .../create-sdk/tests/built-artifacts.e2e.ts | 4 +-- .../create-sdk/tests/create.snapshot.ts | 0 .../create-sdk/tests/create.spec.ts | 0 .../create-sdk/tests/link-workspace.e2e.ts | 2 +- .../create-sdk/tsconfig.json | 0 .../create-sdk/tsdown.config.ts | 0 .../{sdk => scaffold}/helper/README.i18n.yaml | 2 +- packages/{sdk => scaffold}/helper/README.md | 0 .../{sdk => scaffold}/helper/README.zh.md | 0 .../{sdk => scaffold}/helper/package.json | 0 .../helper/src/documents/cordis-yaml-file.ts | 0 .../helper/src/documents/env-file.ts | 0 .../helper/src/documents/package-json-file.ts | 0 .../src/documents/pnpm-workspace-file.ts | 0 .../helper/src/documents/project-file.ts | 0 .../helper/src/documents/tsconfig-file.ts | 0 .../helper/src/features/builtin/app.ts | 0 .../helper/src/features/builtin/helpers.ts | 0 .../helper/src/features/builtin/index.ts | 0 .../helper/src/features/builtin/provider.ts | 0 .../helper/src/features/builtin/spine.ts | 0 .../helper/src/features/define-feature.ts | 0 .../src/features/feature-configurator.ts | 0 .../helper/src/features/feature.ts | 0 .../helper/src/features/registry.ts | 0 .../helper/src/features/resources.ts | 0 packages/{sdk => scaffold}/helper/src/ids.ts | 0 .../{sdk => scaffold}/helper/src/index.ts | 4 +++ .../{sdk => scaffold}/helper/src/invariant.ts | 0 .../src/package-managers/link-workspace.ts | 0 .../src/package-managers/package-manager.ts | 0 .../src/plugins/local-plugin-blueprint.ts | 0 .../helper/src/project/change-set.ts | 0 .../src/project/npm-dependency-policy.ts | 0 .../src/project/project-edit-session.ts | 0 .../helper/src/project/sdk-project.ts | 2 +- .../helper/src/project/types.ts | 0 .../src/questions/clack-nested-multiselect.ts | 0 .../helper/src/questions/clack-prompt-port.ts | 0 .../src/questions/headless-prompt-port.ts | 0 .../helper/src/questions/prompt-port.ts | 0 .../helper/src/questions/question.ts | 0 .../helper/src/templates/assets/README.md.tpl | 0 .../helper/src/templates/assets/gitignore.tpl | 0 .../helper/src/templates/assets/index.ts.tpl | 0 .../assets/local-plugin-tsdown.config.ts.tpl | 0 .../src/templates/assets/local-plugin.ts.tpl | 0 .../src/templates/assets/local-tool.ts.tpl | 0 .../src/templates/assets/package.json.tpl | 0 .../src/templates/assets/persona.txt.tpl | 0 .../templates/assets/tsconfig.base.json.tpl | 0 .../src/templates/assets/tsdown.config.ts.tpl | 0 .../src/templates/assets/yarnrc.yml.tpl | 0 .../helper/src/templates/project-template.ts | 0 .../helper/src/templates/template-assets.ts | 0 .../helper/src/templates/text-template.ts | 0 .../helper/tests/documents.spec.ts | 0 .../helper/tests/headless-prompt-port.spec.ts | 0 .../helper/tests/project.spec.ts | 0 .../helper/tests/questions.spec.ts | 0 .../{sdk => scaffold}/helper/tsconfig.json | 0 .../{sdk => scaffold}/helper/tsdown.config.ts | 0 packages/scaffold/protocol/README.i18n.yaml | 6 ++++ .../protocol}/README.md | 6 ++-- .../protocol}/README.zh.md | 0 .../protocol}/package.json | 0 .../protocol}/src/index.ts | 0 .../protocol}/src/invariant.ts | 0 .../protocol}/src/transport.ts | 0 .../protocol}/src/types.ts | 0 .../protocol}/tests/transport.spec.ts | 0 .../protocol}/tsconfig.json | 0 .../scripts/README.i18n.yaml | 2 +- packages/{sdk => scaffold}/scripts/README.md | 0 .../{sdk => scaffold}/scripts/README.zh.md | 0 .../{sdk => scaffold}/scripts/package.json | 0 .../{sdk => scaffold}/scripts/src/args.ts | 0 packages/{sdk => scaffold}/scripts/src/bin.ts | 0 .../{sdk => scaffold}/scripts/src/build.ts | 0 .../{sdk => scaffold}/scripts/src/command.ts | 0 .../{sdk => scaffold}/scripts/src/config.ts | 0 .../scripts/src/config/config-workflow.ts | 0 .../scripts/src/create-plugin.ts | 0 .../scripts/src/dev/tsdown-config.ts | 0 packages/scaffold/scripts/src/index.ts | 11 ++++++ .../scripts/src/invariant.ts | 0 .../scripts/src/local-plugin-loader-hooks.ts | 0 .../{sdk => scaffold}/scripts/src/runtime.ts | 0 .../scripts/src/telemetry.ts | 0 .../assets/config-install-failure.txt.tpl | 0 .../src/templates/assets/usage.txt.tpl | 0 .../src/templates/dsh-sdk-templates.ts | 0 .../__snapshots__/config.snapshot.ts.snap | 0 .../scripts/tests/config.snapshot.ts | 0 .../scripts/tests/scripts.spec.ts | 0 .../{sdk => scaffold}/scripts/tsconfig.json | 2 +- .../scripts/tsdown.config.ts | 0 packages/scaffold/server/README.i18n.yaml | 6 ++++ .../{ui/jsonrpc => scaffold/server}/README.md | 2 +- .../jsonrpc => scaffold/server}/README.zh.md | 2 +- .../jsonrpc => scaffold/server}/package.json | 0 .../jsonrpc => scaffold/server}/src/index.ts | 4 +++ .../server}/src/invariant.ts | 0 .../jsonrpc => scaffold/server}/src/server.ts | 0 .../server}/tests/built-scope-carrier.e2e.ts | 2 +- .../server}/tests/plugin-apply.spec.ts | 0 .../server}/tests/plugin-shape.spec.ts | 0 .../server}/tests/server.spec.ts | 0 .../jsonrpc => scaffold/server}/tsconfig.json | 2 +- .../telemetry/README.i18n.yaml | 2 +- .../{sdk => scaffold}/telemetry/README.md | 0 .../{sdk => scaffold}/telemetry/README.zh.md | 0 .../{sdk => scaffold}/telemetry/package.json | 0 .../telemetry/src/anonymous-id.ts | 0 .../telemetry/src/consent-resolver.ts | 0 .../{sdk => scaffold}/telemetry/src/index.ts | 4 +++ .../telemetry/src/invariant.ts | 0 .../telemetry/src/payload.ts | 0 .../telemetry/src/reporter.ts | 0 .../telemetry/src/secret-redactor.ts | 0 .../telemetry/tests/anonymous-id.spec.ts | 0 .../telemetry/tests/consent-resolver.spec.ts | 0 .../telemetry/tests/payload.spec.ts | 0 .../telemetry/tests/reporter.spec.ts | 0 .../telemetry/tests/secret-redactor.spec.ts | 0 .../{sdk => scaffold}/telemetry/tsconfig.json | 0 packages/sdk/README.zh.md | 16 --------- packages/sdk/scripts/src/index.ts | 7 ---- packages/sdk/sdk-protocol/README.i18n.yaml | 6 ---- .../subagent-dsh-sdk/README.i18n.yaml | 4 +-- packages/subagent/subagent-dsh-sdk/README.md | 2 +- .../subagent/subagent-dsh-sdk/README.zh.md | 2 +- .../tests/subagent-dsh-sdk.spec.ts | 2 +- .../subagent/subagent-dsh-sdk/tsconfig.json | 4 +-- .../subagent/subagent-inprocess/tsconfig.json | 2 +- .../typert/generator/tests/type-model.spec.ts | 22 ++++++------ packages/ui/jsonrpc/README.i18n.yaml | 6 ---- packages/ui/permission/README.i18n.yaml | 6 ---- packages/ui/user-interaction/README.i18n.yaml | 6 ---- scripts/gen-cordis-catalog.ts | 10 +++--- scripts/gen-third-party-notices.spec.ts | 2 +- scripts/gen-tool-catalog.ts | 2 +- scripts/run-gates.ts | 2 +- scripts/type-equiv.manifest.json | 36 +++++++++---------- .../verify-package-readme-model-experience.ts | 18 +++++----- tsconfig.base.json | 23 ++++++++---- tsconfig.host.json | 26 +++++++------- vendor/README.md | 8 ++--- vitest.config.ts | 10 +++--- vitest.snapshot.config.ts | 2 +- 351 files changed, 368 insertions(+), 311 deletions(-) rename packages/{sdk => boot}/README.i18n.yaml (57%) create mode 100644 packages/boot/README.md create mode 100644 packages/boot/README.zh.md rename packages/{ui => boot}/app-boot/README.i18n.yaml (81%) rename packages/{ui => boot}/app-boot/README.md (100%) rename packages/{ui => boot}/app-boot/README.zh.md (100%) rename packages/{ui => boot}/app-boot/package.json (100%) rename packages/{ui => boot}/app-boot/src/index.ts (100%) rename packages/{ui => boot}/app-boot/src/invariant.ts (100%) rename packages/{ui => boot}/app-boot/src/profile.ts (100%) rename packages/{ui => boot}/app-boot/tests/app-boot.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/config-dump.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/config-reload.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/hmr-config.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/profile.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/repository-cache.spec.ts (100%) rename packages/{ui => boot}/app-boot/tests/user-patches.spec.ts (100%) rename packages/{ui => boot}/app-boot/tsconfig.json (100%) rename packages/{ui => boot}/app-boot/tsdown.config.ts (100%) create mode 100644 packages/interaction/README.i18n.yaml rename packages/{ui => interaction}/README.md (71%) rename packages/{ui => interaction}/README.zh.md (69%) rename packages/{ui => interaction}/commands/README.i18n.yaml (79%) rename packages/{ui => interaction}/commands/README.md (100%) rename packages/{ui => interaction}/commands/README.zh.md (100%) rename packages/{ui => interaction}/commands/package.json (100%) rename packages/{ui => interaction}/commands/src/brand.ts (100%) rename packages/{ui => interaction}/commands/src/index.ts (100%) rename packages/{ui => interaction}/commands/src/invariant.ts (100%) rename packages/{ui => interaction}/commands/tests/commands.spec.ts (100%) rename packages/{ui => interaction}/commands/tests/invariant.spec.ts (100%) rename packages/{ui => interaction}/commands/tsconfig.json (100%) create mode 100644 packages/interaction/permission/README.i18n.yaml rename packages/{ui => interaction}/permission/README.md (100%) rename packages/{ui => interaction}/permission/README.zh.md (100%) rename packages/{ui => interaction}/permission/package.json (100%) rename packages/{ui => interaction}/permission/src/client.ts (100%) rename packages/{ui => interaction}/permission/src/index.ts (100%) rename packages/{ui => interaction}/permission/src/invariant.ts (100%) rename packages/{ui => interaction}/permission/src/types.ts (100%) rename packages/{ui => interaction}/permission/tests/invariant.spec.ts (100%) rename packages/{ui => interaction}/permission/tests/permission.spec.ts (100%) rename packages/{ui => interaction}/permission/tests/projection.spec.ts (100%) rename packages/{ui => interaction}/permission/tsconfig.json (100%) rename packages/{ui => interaction}/tool-ask-user/README.i18n.yaml (79%) rename packages/{ui => interaction}/tool-ask-user/README.md (100%) rename packages/{ui => interaction}/tool-ask-user/README.zh.md (100%) rename packages/{ui => interaction}/tool-ask-user/package.json (100%) rename packages/{ui => interaction}/tool-ask-user/src/index.ts (100%) rename packages/{ui => interaction}/tool-ask-user/src/invariant.ts (100%) rename packages/{ui => interaction}/tool-ask-user/tests/tool-ask-user.spec.ts (100%) rename packages/{ui => interaction}/tool-ask-user/tsconfig.json (100%) rename packages/{ui => interaction}/user-approval/README.i18n.yaml (79%) rename packages/{ui => interaction}/user-approval/README.md (100%) rename packages/{ui => interaction}/user-approval/README.zh.md (100%) rename packages/{ui => interaction}/user-approval/package.json (100%) rename packages/{ui => interaction}/user-approval/src/index.ts (100%) rename packages/{ui => interaction}/user-approval/src/invariant.ts (100%) rename packages/{ui => interaction}/user-approval/src/types.ts (100%) rename packages/{ui => interaction}/user-approval/tests/approval.spec.ts (100%) rename packages/{ui => interaction}/user-approval/tests/invariant.spec.ts (100%) rename packages/{ui => interaction}/user-approval/tsconfig.json (100%) rename packages/{ui => interaction}/user-approval/tsdown.config.ts (100%) create mode 100644 packages/interaction/user-interaction/README.i18n.yaml rename packages/{ui => interaction}/user-interaction/README.md (100%) rename packages/{ui => interaction}/user-interaction/README.zh.md (100%) rename packages/{ui => interaction}/user-interaction/package.json (100%) rename packages/{ui => interaction}/user-interaction/src/index.ts (100%) rename packages/{ui => interaction}/user-interaction/src/invariant.ts (100%) rename packages/{ui => interaction}/user-interaction/src/types.ts (100%) rename packages/{ui => interaction}/user-interaction/tests/user-interaction.spec.ts (100%) rename packages/{ui => interaction}/user-interaction/tsconfig.json (100%) rename packages/{ui => scaffold}/README.i18n.yaml (57%) rename packages/{sdk => scaffold}/README.md (64%) create mode 100644 packages/scaffold/README.zh.md rename packages/{sdk/sdk-client => scaffold/client}/README.i18n.yaml (80%) rename packages/{sdk/sdk-client => scaffold/client}/README.md (100%) rename packages/{sdk/sdk-client => scaffold/client}/README.zh.md (100%) rename packages/{sdk/sdk-client => scaffold/client}/package.json (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/api.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/client.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/dispose.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/index.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/invariant.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/src/types.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/tests/dispose.spec.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/tests/fake-runtime.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/tests/sdk-client.spec.ts (100%) rename packages/{sdk/sdk-client => scaffold/client}/tsconfig.json (93%) rename packages/{sdk => scaffold}/create-sdk/README.i18n.yaml (80%) rename packages/{sdk => scaffold}/create-sdk/README.md (100%) rename packages/{sdk => scaffold}/create-sdk/README.zh.md (100%) rename packages/{sdk => scaffold}/create-sdk/package.json (100%) rename packages/{sdk => scaffold}/create-sdk/src/args.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/bin.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/command.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/create-questions.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/create-wizard.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/headless.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/index.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/invariant.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/project-scaffolder.ts (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/assets/created.txt.tpl (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/assets/install-question.txt.tpl (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/assets/next-steps.txt.tpl (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/assets/setup-failure.txt.tpl (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/assets/usage.txt.tpl (100%) rename packages/{sdk => scaffold}/create-sdk/src/templates/create-templates.ts (100%) rename packages/{sdk => scaffold}/create-sdk/tests/built-artifacts.e2e.ts (87%) rename packages/{sdk => scaffold}/create-sdk/tests/create.snapshot.ts (100%) rename packages/{sdk => scaffold}/create-sdk/tests/create.spec.ts (100%) rename packages/{sdk => scaffold}/create-sdk/tests/link-workspace.e2e.ts (98%) rename packages/{sdk => scaffold}/create-sdk/tsconfig.json (100%) rename packages/{sdk => scaffold}/create-sdk/tsdown.config.ts (100%) rename packages/{sdk => scaffold}/helper/README.i18n.yaml (80%) rename packages/{sdk => scaffold}/helper/README.md (100%) rename packages/{sdk => scaffold}/helper/README.zh.md (100%) rename packages/{sdk => scaffold}/helper/package.json (100%) rename packages/{sdk => scaffold}/helper/src/documents/cordis-yaml-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/documents/env-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/documents/package-json-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/documents/pnpm-workspace-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/documents/project-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/documents/tsconfig-file.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/builtin/app.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/builtin/helpers.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/builtin/index.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/builtin/provider.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/builtin/spine.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/define-feature.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/feature-configurator.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/feature.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/registry.ts (100%) rename packages/{sdk => scaffold}/helper/src/features/resources.ts (100%) rename packages/{sdk => scaffold}/helper/src/ids.ts (100%) rename packages/{sdk => scaffold}/helper/src/index.ts (86%) rename packages/{sdk => scaffold}/helper/src/invariant.ts (100%) rename packages/{sdk => scaffold}/helper/src/package-managers/link-workspace.ts (100%) rename packages/{sdk => scaffold}/helper/src/package-managers/package-manager.ts (100%) rename packages/{sdk => scaffold}/helper/src/plugins/local-plugin-blueprint.ts (100%) rename packages/{sdk => scaffold}/helper/src/project/change-set.ts (100%) rename packages/{sdk => scaffold}/helper/src/project/npm-dependency-policy.ts (100%) rename packages/{sdk => scaffold}/helper/src/project/project-edit-session.ts (100%) rename packages/{sdk => scaffold}/helper/src/project/sdk-project.ts (99%) rename packages/{sdk => scaffold}/helper/src/project/types.ts (100%) rename packages/{sdk => scaffold}/helper/src/questions/clack-nested-multiselect.ts (100%) rename packages/{sdk => scaffold}/helper/src/questions/clack-prompt-port.ts (100%) rename packages/{sdk => scaffold}/helper/src/questions/headless-prompt-port.ts (100%) rename packages/{sdk => scaffold}/helper/src/questions/prompt-port.ts (100%) rename packages/{sdk => scaffold}/helper/src/questions/question.ts (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/README.md.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/gitignore.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/index.ts.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/local-plugin-tsdown.config.ts.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/local-plugin.ts.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/local-tool.ts.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/package.json.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/persona.txt.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/tsconfig.base.json.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/tsdown.config.ts.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/assets/yarnrc.yml.tpl (100%) rename packages/{sdk => scaffold}/helper/src/templates/project-template.ts (100%) rename packages/{sdk => scaffold}/helper/src/templates/template-assets.ts (100%) rename packages/{sdk => scaffold}/helper/src/templates/text-template.ts (100%) rename packages/{sdk => scaffold}/helper/tests/documents.spec.ts (100%) rename packages/{sdk => scaffold}/helper/tests/headless-prompt-port.spec.ts (100%) rename packages/{sdk => scaffold}/helper/tests/project.spec.ts (100%) rename packages/{sdk => scaffold}/helper/tests/questions.spec.ts (100%) rename packages/{sdk => scaffold}/helper/tsconfig.json (100%) rename packages/{sdk => scaffold}/helper/tsdown.config.ts (100%) create mode 100644 packages/scaffold/protocol/README.i18n.yaml rename packages/{sdk/sdk-protocol => scaffold/protocol}/README.md (90%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/README.zh.md (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/package.json (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/src/index.ts (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/src/invariant.ts (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/src/transport.ts (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/src/types.ts (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/tests/transport.spec.ts (100%) rename packages/{sdk/sdk-protocol => scaffold/protocol}/tsconfig.json (100%) rename packages/{sdk => scaffold}/scripts/README.i18n.yaml (80%) rename packages/{sdk => scaffold}/scripts/README.md (100%) rename packages/{sdk => scaffold}/scripts/README.zh.md (100%) rename packages/{sdk => scaffold}/scripts/package.json (100%) rename packages/{sdk => scaffold}/scripts/src/args.ts (100%) rename packages/{sdk => scaffold}/scripts/src/bin.ts (100%) rename packages/{sdk => scaffold}/scripts/src/build.ts (100%) rename packages/{sdk => scaffold}/scripts/src/command.ts (100%) rename packages/{sdk => scaffold}/scripts/src/config.ts (100%) rename packages/{sdk => scaffold}/scripts/src/config/config-workflow.ts (100%) rename packages/{sdk => scaffold}/scripts/src/create-plugin.ts (100%) rename packages/{sdk => scaffold}/scripts/src/dev/tsdown-config.ts (100%) create mode 100644 packages/scaffold/scripts/src/index.ts rename packages/{sdk => scaffold}/scripts/src/invariant.ts (100%) rename packages/{sdk => scaffold}/scripts/src/local-plugin-loader-hooks.ts (100%) rename packages/{sdk => scaffold}/scripts/src/runtime.ts (100%) rename packages/{sdk => scaffold}/scripts/src/telemetry.ts (100%) rename packages/{sdk => scaffold}/scripts/src/templates/assets/config-install-failure.txt.tpl (100%) rename packages/{sdk => scaffold}/scripts/src/templates/assets/usage.txt.tpl (100%) rename packages/{sdk => scaffold}/scripts/src/templates/dsh-sdk-templates.ts (100%) rename packages/{sdk => scaffold}/scripts/tests/__snapshots__/config.snapshot.ts.snap (100%) rename packages/{sdk => scaffold}/scripts/tests/config.snapshot.ts (100%) rename packages/{sdk => scaffold}/scripts/tests/scripts.spec.ts (100%) rename packages/{sdk => scaffold}/scripts/tsconfig.json (88%) rename packages/{sdk => scaffold}/scripts/tsdown.config.ts (100%) create mode 100644 packages/scaffold/server/README.i18n.yaml rename packages/{ui/jsonrpc => scaffold/server}/README.md (95%) rename packages/{ui/jsonrpc => scaffold/server}/README.zh.md (95%) rename packages/{ui/jsonrpc => scaffold/server}/package.json (100%) rename packages/{ui/jsonrpc => scaffold/server}/src/index.ts (92%) rename packages/{ui/jsonrpc => scaffold/server}/src/invariant.ts (100%) rename packages/{ui/jsonrpc => scaffold/server}/src/server.ts (100%) rename packages/{ui/jsonrpc => scaffold/server}/tests/built-scope-carrier.e2e.ts (98%) rename packages/{ui/jsonrpc => scaffold/server}/tests/plugin-apply.spec.ts (100%) rename packages/{ui/jsonrpc => scaffold/server}/tests/plugin-shape.spec.ts (100%) rename packages/{ui/jsonrpc => scaffold/server}/tests/server.spec.ts (100%) rename packages/{ui/jsonrpc => scaffold/server}/tsconfig.json (93%) rename packages/{sdk => scaffold}/telemetry/README.i18n.yaml (80%) rename packages/{sdk => scaffold}/telemetry/README.md (100%) rename packages/{sdk => scaffold}/telemetry/README.zh.md (100%) rename packages/{sdk => scaffold}/telemetry/package.json (100%) rename packages/{sdk => scaffold}/telemetry/src/anonymous-id.ts (100%) rename packages/{sdk => scaffold}/telemetry/src/consent-resolver.ts (100%) rename packages/{sdk => scaffold}/telemetry/src/index.ts (82%) rename packages/{sdk => scaffold}/telemetry/src/invariant.ts (100%) rename packages/{sdk => scaffold}/telemetry/src/payload.ts (100%) rename packages/{sdk => scaffold}/telemetry/src/reporter.ts (100%) rename packages/{sdk => scaffold}/telemetry/src/secret-redactor.ts (100%) rename packages/{sdk => scaffold}/telemetry/tests/anonymous-id.spec.ts (100%) rename packages/{sdk => scaffold}/telemetry/tests/consent-resolver.spec.ts (100%) rename packages/{sdk => scaffold}/telemetry/tests/payload.spec.ts (100%) rename packages/{sdk => scaffold}/telemetry/tests/reporter.spec.ts (100%) rename packages/{sdk => scaffold}/telemetry/tests/secret-redactor.spec.ts (100%) rename packages/{sdk => scaffold}/telemetry/tsconfig.json (100%) delete mode 100644 packages/sdk/README.zh.md delete mode 100644 packages/sdk/scripts/src/index.ts delete mode 100644 packages/sdk/sdk-protocol/README.i18n.yaml delete mode 100644 packages/ui/jsonrpc/README.i18n.yaml delete mode 100644 packages/ui/permission/README.i18n.yaml delete mode 100644 packages/ui/user-interaction/README.i18n.yaml diff --git a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md index 3cf9548d4d..9b2c456d88 100644 --- a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md +++ b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md @@ -7,7 +7,7 @@ English | [中文](2026-07-27-tui-chat-channel-module-split.zh.md) ## Problem -`packages/ui/tui/src/index.ts` had grown past 2000 lines. Most of it was one `createTuiChat` factory: a ~1600-line closure holding roughly forty mutable variables and as many nested closures. Model selection, the ask-user-question queue, and session resume were tangled into that single scope, so a reader could not follow any one concern without holding the whole file in their head, and unrelated edits collided. A prior pass had grouped `src/` into `components/`, `session/`, `extension/`, but the entry file itself and the loose top-level input files (`autocomplete.ts`, `file-autocomplete.ts`, `skill-invocation.ts`, `xml-tool-output.ts`) were untouched. +`packages/interaction/tui/src/index.ts` had grown past 2000 lines. Most of it was one `createTuiChat` factory: a ~1600-line closure holding roughly forty mutable variables and as many nested closures. Model selection, the ask-user-question queue, and session resume were tangled into that single scope, so a reader could not follow any one concern without holding the whole file in their head, and unrelated edits collided. A prior pass had grouped `src/` into `components/`, `session/`, `extension/`, but the entry file itself and the loose top-level input files (`autocomplete.ts`, `file-autocomplete.ts`, `skill-invocation.ts`, `xml-tool-output.ts`) were untouched. ## Decision diff --git a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md index d5de02bd0f..44e8ffeac4 100644 --- a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md +++ b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md @@ -7,7 +7,7 @@ Archived: 2026-08-04 ## Problem -`packages/ui/tui/src/index.ts` 已超过 2000 行,其中绝大部分是单个 `createTuiChat` 工厂:一个约 1600 行的闭包,持有约四十个可变变量以及同等数量的嵌套闭包。模型选择、ask-user-question 队列、会话恢复都缠绕在这一个作用域里,读者无法在不把整份文件装进脑子的前提下理清任何单一关注点,互不相关的改动也会彼此冲突。此前一轮已把 `src/` 归组为 `components/`、`session/`、`extension/`,但入口文件本身以及散落在顶层的输入相关文件(`autocomplete.ts`、`file-autocomplete.ts`、`skill-invocation.ts`、`xml-tool-output.ts`)未动。 +`packages/interaction/tui/src/index.ts` 已超过 2000 行,其中绝大部分是单个 `createTuiChat` 工厂:一个约 1600 行的闭包,持有约四十个可变变量以及同等数量的嵌套闭包。模型选择、ask-user-question 队列、会话恢复都缠绕在这一个作用域里,读者无法在不把整份文件装进脑子的前提下理清任何单一关注点,互不相关的改动也会彼此冲突。此前一轮已把 `src/` 归组为 `components/`、`session/`、`extension/`,但入口文件本身以及散落在顶层的输入相关文件(`autocomplete.ts`、`file-autocomplete.ts`、`skill-invocation.ts`、`xml-tool-output.ts`)未动。 ## Decision diff --git a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md index 62784ed7cd..998e9dfcdc 100644 --- a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md +++ b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md @@ -11,7 +11,7 @@ The TUI rendered transcript notices for `error`, `aborted`, `max-tokens`, `rejec ## Decision -The `turn/end` case in `packages/ui/tui/src/index.ts` switches on the reason's discriminant and covers every kind: `completed` stays silent because the settled assistant message and its `Completed` timing header already present that outcome; `disposed` appends `Turn stopped: the agent was disposed.`; and the merge-extensible default appends `Turn ended: .` so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices. +The `turn/end` case in `packages/interaction/tui/src/index.ts` switches on the reason's discriminant and covers every kind: `completed` stays silent because the settled assistant message and its `Completed` timing header already present that outcome; `disposed` appends `Turn stopped: the agent was disposed.`; and the merge-extensible default appends `Turn ended: .` so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices. ## Alternatives considered diff --git a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md index 162ba4ed44..ad53755259 100644 --- a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md +++ b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md @@ -11,7 +11,7 @@ TUI 会为 `error`、`aborted`、`max-tokens`、`rejected`、`interrupted` 这 ## 决策 -`packages/ui/tui/src/index.ts` 中的 `turn/end` 分支按 reason 的判别字段做 switch,覆盖每一种 kind:`completed` 保持沉默,因为已定稿的助手消息及其 `Completed` 计时头部已经呈现了这一结果;`disposed` 追加 `Turn stopped: the agent was disposed.`;merge 扩展的 default 分支追加 `Turn ended: .`,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。 +`packages/interaction/tui/src/index.ts` 中的 `turn/end` 分支按 reason 的判别字段做 switch,覆盖每一种 kind:`completed` 保持沉默,因为已定稿的助手消息及其 `Completed` 计时头部已经呈现了这一结果;`disposed` 追加 `Turn stopped: the agent was disposed.`;merge 扩展的 default 分支追加 `Turn ended: .`,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。 ## 备选方案 diff --git a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md index 1a04e738a2..184a05ed23 100644 --- a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md +++ b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md @@ -15,7 +15,7 @@ The interactive channel must remain a Cordis plugin over the same agent, session ## Decision -DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior. +DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior. The package is a terminal front door, not a complete application. A host mounts `@deepseek-ai/dsh-tui` before its configured agent and composes the backends, tools, and policies around it. The product CLI currently ships no terminal composition; non-interactive tasks use headless mode, Web owns the installed human surface, and ACP remains a separate automation protocol. diff --git a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md index 75fc3d47aa..6ac3eefa51 100644 --- a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md +++ b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md @@ -15,7 +15,7 @@ Archived: 2026-08-04 ## 决策 -DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。 +DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。 该包是终端入口,而不是完整应用。宿主在已配置 agent 之前挂载 `@deepseek-ai/dsh-tui`,并围绕它组合后端、工具和策略。产品 CLI 目前不交付终端组合;非交互任务使用 headless 模式,Web 是已安装产品中面向人的界面,而 ACP 仍是独立的自动化协议。 diff --git a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md index 9308df7fb4..2c8e92671f 100644 --- a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md +++ b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md @@ -13,7 +13,7 @@ The TUI platform contract must follow the runtime shipped to users rather than t ## Decision -[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) supports interactive terminals on Windows as well as macOS and Linux. The product continues to use pi-tui's `ProcessTerminal`; on Windows it enables virtual-terminal input after raw mode and avoids the Unix-only `SIGWINCH` refresh. DeepSeek Harness adds no platform rejection or reduced Windows mode. +[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) supports interactive terminals on Windows as well as macOS and Linux. The product continues to use pi-tui's `ProcessTerminal`; on Windows it enables virtual-terminal input after raw mode and avoids the Unix-only `SIGWINCH` refresh. DeepSeek Harness adds no platform rejection or reduced Windows mode. The real Loader smoke selects a native pseudo-terminal boundary by host. macOS and Linux retain the Python POSIX PTY driver. Windows uses `node-pty` and ConPTY. Both drivers receive the same launch command, environment, terminal dimensions, marker-gated input actions, timeout, expected exit code, and output assertions, and all three smoke scenarios run on every supported platform. diff --git a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md index 8b4a1eb4ce..cb5d05da2b 100644 --- a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md +++ b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md @@ -13,7 +13,7 @@ TUI 平台契约必须以交付给用户的运行时为准,而不是取决于 ## 决策 -[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 在 Windows、macOS 和 Linux 上均支持交互式终端。产品继续使用 pi-tui 的 `ProcessTerminal`;在 Windows 上,它会在进入原始模式后启用虚拟终端输入,并避开仅适用于 Unix 的 `SIGWINCH` 刷新。DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。 +[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 在 Windows、macOS 和 Linux 上均支持交互式终端。产品继续使用 pi-tui 的 `ProcessTerminal`;在 Windows 上,它会在进入原始模式后启用虚拟终端输入,并避开仅适用于 Unix 的 `SIGWINCH` 刷新。DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。 真实 Loader 冒烟测试根据宿主选择原生伪终端边界。macOS 和 Linux 继续使用 Python POSIX PTY 驱动,Windows 则使用 `node-pty` 和 ConPTY。两种驱动接收相同的启动命令、环境、终端尺寸、以标记为触发条件的输入动作、超时、预期退出码和输出断言;3 个冒烟场景都会在每个受支持平台上运行。 diff --git a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md index e151f92d0d..038d12a561 100644 --- a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md +++ b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md @@ -11,7 +11,7 @@ The [skill system](2026-07-05-skill-system.md) shipped with model-initiated load ## Decision -The [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) front door owns a `/skill: [instructions]` command. On submit it loads the named skill and delivers one text block as a user turn — sent with `agent.send()` while idle and `agent.steer()` while running, the same rule as ordinary editor input. The block is `renderSkillInvocation(skill, instructions)`: a `` element wrapping the skill body, preceded by one resource-base line when the provider exposes one, with the user's trailing text appended after a blank line. The command is a TUI-only affordance; it adds no model-facing tool. Its visibility and loading policy comes from the shared [independent model and user skill invocation policy](2026-07-28-skill-invocation-policy.md). +The [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) front door owns a `/skill: [instructions]` command. On submit it loads the named skill and delivers one text block as a user turn — sent with `agent.send()` while idle and `agent.steer()` while running, the same rule as ordinary editor input. The block is `renderSkillInvocation(skill, instructions)`: a `` element wrapping the skill body, preceded by one resource-base line when the provider exposes one, with the user's trailing text appended after a blank line. The command is a TUI-only affordance; it adds no model-facing tool. Its visibility and loading policy comes from the shared [independent model and user skill invocation policy](2026-07-28-skill-invocation-policy.md). The TUI reads the skill service through `ctx.get('skills')`, not a declared injection, because skills mount conditionally: a deployment without the registry keeps a working front door, and `/skill:` there reports that skills are unavailable rather than failing to mount. `createTuiChat` is synchronous while `ctx.skills.list()` is async, so autocomplete seeds the static slash commands immediately and rebuilds the provider with `skill:` entries once the catalog resolves; a resolution that arrives after disposal is dropped, and a rejected lookup keeps the base commands. diff --git a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md index ac1c4da948..af01642c07 100644 --- a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md +++ b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md @@ -11,7 +11,7 @@ Archived: 2026-08-04 ## Decision -[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 前门拥有一条 `/skill: [instructions]` 命令。提交时它加载指定的 skill,并投递一个文本块作为用户轮次——空闲时用 `agent.send()` 发送、运行中用 `agent.steer()` 中途引导,与普通编辑器输入遵循同一规则。该文本块由 `renderSkillInvocation(skill, instructions)` 生成:一个包裹 skill 正文的 `` 元素,当提供方暴露资源基址时在其前加一行资源基址行,用户尾随的文本在空行之后追加。该命令是 TUI 独有的功能;它不新增任何面向模型的工具。其可见性和加载策略来自共享的[模型与用户独立 skill 调用策略](2026-07-28-skill-invocation-policy.md)。 +[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 前门拥有一条 `/skill: [instructions]` 命令。提交时它加载指定的 skill,并投递一个文本块作为用户轮次——空闲时用 `agent.send()` 发送、运行中用 `agent.steer()` 中途引导,与普通编辑器输入遵循同一规则。该文本块由 `renderSkillInvocation(skill, instructions)` 生成:一个包裹 skill 正文的 `` 元素,当提供方暴露资源基址时在其前加一行资源基址行,用户尾随的文本在空行之后追加。该命令是 TUI 独有的功能;它不新增任何面向模型的工具。其可见性和加载策略来自共享的[模型与用户独立 skill 调用策略](2026-07-28-skill-invocation-policy.md)。 TUI 通过 `ctx.get('skills')` 读取 skill 服务,而非声明式注入,因为 skill 是条件挂载的:没有注册表的部署仍保有可用的前门,此时 `/skill:` 会报告 skill 不可用,而不是挂载失败。`createTuiChat` 是同步的,而 `ctx.skills.list()` 是异步的,所以自动补全先立即种入静态斜杠命令,待目录解析完成后再用 `skill:` 条目重建 provider(提供方);在 dispose(资源释放)之后才到达的解析结果会被丢弃,而被拒绝的查找会保留基础命令。 diff --git a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md index 2ef926cff9..ff4a310f08 100644 --- a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md +++ b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md @@ -13,7 +13,7 @@ The TUI assistant message opened with a single header line joining the `Assistan **Split the label from the timing; render the timing as the message's trailing line.** -`AssistantMessageComponent` (packages/ui/tui/src/index.ts) now emits the bold `Assistant` label as the first line and appends the dim timing string (already assembled by `StreamingAssistantComponent.rebuild()` as `header`, including the `· Completed …` suffix when settled) as the last child, after reasoning and text. The timing content, bucket-hiding, and completion-time behavior are unchanged — only its position moved from the top to the bottom of the message. +`AssistantMessageComponent` (packages/interaction/tui/src/index.ts) now emits the bold `Assistant` label as the first line and appends the dim timing string (already assembled by `StreamingAssistantComponent.rebuild()` as `header`, including the `· Completed …` suffix when settled) as the last child, after reasoning and text. The timing content, bucket-hiding, and completion-time behavior are unchanged — only its position moved from the top to the bottom of the message. ## Alternatives considered diff --git a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md index 282ac1e65b..08ced9603d 100644 --- a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md +++ b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md @@ -13,7 +13,7 @@ TUI 的助手消息此前以一行开头,把 `Assistant` 标签和步骤计时 **把标签与计时拆开;计时作为消息的末行渲染。** -`AssistantMessageComponent`(packages/ui/tui/src/index.ts)现在把加粗的 `Assistant` 标签作为首行,并把暗色的计时串(仍由 `StreamingAssistantComponent.rebuild()` 组装为 `header`,settled 时含 `· Completed …` 后缀)作为最后一个子节点,追加在 reasoning 与正文之后。计时内容、隐藏零值桶以及完成时间的行为均不变——仅位置从消息顶部移到底部。 +`AssistantMessageComponent`(packages/interaction/tui/src/index.ts)现在把加粗的 `Assistant` 标签作为首行,并把暗色的计时串(仍由 `StreamingAssistantComponent.rebuild()` 组装为 `header`,settled 时含 `· Completed …` 后缀)作为最后一个子节点,追加在 reasoning 与正文之后。计时内容、隐藏零值桶以及完成时间的行为均不变——仅位置从消息顶部移到底部。 ## Alternatives considered diff --git a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md index ca5edc50bf..76cc213ba8 100644 --- a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md +++ b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md @@ -11,7 +11,7 @@ Roughly ten e2e/smoke files re-derived the same spawn-collect-timeout choreograp Two related test-infra hand-rolls compounded the case: -- `packages/support/llm-mock-server/src/cli.ts` hand-tokenized 17 value-taking `--flag value` options plus boolean flags (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/sdk/scripts`). +- `packages/support/llm-mock-server/src/cli.ts` hand-tokenized 17 value-taking `--flag value` options plus boolean flags (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/scaffold/scripts`). - `apps/web/tests/smoke-real.e2e.ts` and `apps/web/tests/scaffold.ts` carried two verbatim copies of a regex `.env` parser (~20 lines) where the `process.loadEnvFile` builtin has exactly the required no-override semantics — and the vitest e2e/snapshot/web configs already load root `.env` with it before these files run, making the copies dead. - The snapshot harness hand-rolled three poll-until-deadline loops (`waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile` in `packages/support/acp-snapshot/src/harness.ts`, ~55 lines) plus `waitForFile` in `crash-recovery.e2e.ts`, where `vi.waitFor`/`expect.poll` cover the shape — vitest is already a runtime dependency of `dsh-acp-snapshot`, so this adds nothing. diff --git a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md index 186e5cd560..f597fd1e08 100644 --- a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md +++ b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md @@ -11,7 +11,7 @@ Archived: 2026-08-07 另有两处相关的测试基础设施手写代码进一步强化了替换的理由: -- `packages/support/llm-mock-server/src/cli.ts` 曾手工逐个切分 17 个带值的 `--flag value` 选项外加若干布尔标志(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/sdk/scripts`)。 +- `packages/support/llm-mock-server/src/cli.ts` 曾手工逐个切分 17 个带值的 `--flag value` 选项外加若干布尔标志(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/scaffold/scripts`)。 - `apps/web/tests/smoke-real.e2e.ts` 与 `apps/web/tests/scaffold.ts` 曾携带两份逐字相同的正则 `.env` 解析器拷贝(约 20 行),而内置的 `process.loadEnvFile` 恰好具备所需的「不覆盖已有值」语义;并且 vitest 的 e2e/snapshot/web 配置在这些文件运行之前就已用它加载了根 `.env`,这两份拷贝实为死代码。 - 快照 harness 曾手写三个「轮询直到截止时间」的循环(`packages/support/acp-snapshot/src/harness.ts` 中的 `waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile`,约 55 行),外加 `crash-recovery.e2e.ts` 中的 `waitForFile`,而 `vi.waitFor`/`expect.poll` 正好覆盖这种形态;vitest 本来就是 `dsh-acp-snapshot` 的运行时依赖,因此这不新增任何东西。 diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml index 7a0b74dfaf..08d8319d33 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md -2026-07-10-single-file-executable-sdk-runtime-distribution.md: f3da981c478ef08672a82f23ab9cd42e0f38ebab -2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 5a8e15aab79875bdb08e6347199924c4215a2705 +2026-07-10-single-file-executable-sdk-runtime-distribution.md: fe09e1b83f4f0a9f867f78351f643b1bd26d7c9d +2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: ee49d7ea8fade9d3e323e8c6921479b8053809af diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md index f3da981c47..fe09e1b83f 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md @@ -27,8 +27,8 @@ Terminology reminder: pkg's `/snapshot` VFS has nothing to do with this repo's t The deterministic protocol implementation (`server.ts` / `transport.ts`) lands as two packages on the existing `acp/acp` + `examples/acp-demo` pattern — the serving surface is itself a plugin: -- [`packages/ui/jsonrpc`](../../../../packages/ui/jsonrpc/README.md) (`@deepseek-ai/dsh-jsonrpc`): the pure protocol plugin; on apply it mounts `HarnessSdkServer` plus a line-delimited JSON-RPC transport on the process stdio, with disposal through `ctx.effect()`. Whether to serve is decided by `cordis.yml`; a yml that does not mount it is a legitimate process that does not serve. Protocol-level exit belongs to the plugin (after answering and flushing the `shutdown` response it disposes the root runtime so persistence drains, then `exit(0)`; an HMR-style unload only stops the service without exiting the process). -- [`packages/examples/jsonrpc-demo`](../../../../packages/examples/jsonrpc-demo/README.md) (`@deepseek-ai/dsh-jsonrpc-demo`): a thin app bin — `installFailLoud` + `loadEnv` + config discovery + `boot()` from [`dsh-app-boot`](../../../../packages/ui/app-boot/src/index.ts), done once boot completes; the server is brought up by the `dsh-jsonrpc` entry in the yml. Its only dependency is app-boot. Process-level exit belongs to the bin (stdin EOF/SIGTERM → dispose then 0, SIGINT → 130). +- [`packages/scaffold/server`](../../../../packages/scaffold/server/README.md) (`@deepseek-ai/dsh-jsonrpc`): the pure protocol plugin; on apply it mounts `HarnessSdkServer` plus a line-delimited JSON-RPC transport on the process stdio, with disposal through `ctx.effect()`. Whether to serve is decided by `cordis.yml`; a yml that does not mount it is a legitimate process that does not serve. Protocol-level exit belongs to the plugin (after answering and flushing the `shutdown` response it disposes the root runtime so persistence drains, then `exit(0)`; an HMR-style unload only stops the service without exiting the process). +- [`packages/examples/jsonrpc-demo`](../../../../packages/examples/jsonrpc-demo/README.md) (`@deepseek-ai/dsh-jsonrpc-demo`): a thin app bin — `installFailLoud` + `loadEnv` + config discovery + `boot()` from [`dsh-app-boot`](../../../../packages/boot/app-boot/src/index.ts), done once boot completes; the server is brought up by the `dsh-jsonrpc` entry in the yml. Its only dependency is app-boot. Process-level exit belongs to the bin (stdin EOF/SIGTERM → dispose then 0, SIGINT → 130). Config discovery has two channels and fails loudly when both are missing: the `DSH_CORDIS_CONFIG` environment variable first (the SDK client convention), then an argv positional argument; no default path and no built-in fallback whatsoever — "the plugins actually booted are decided by an external cordis.yml" is a hard semantic. diff --git a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md index 5a8e15aab7..ee49d7ea8f 100644 --- a/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md @@ -27,8 +27,8 @@ exe 使用 [@yao-pkg/pkg](https://github.com/yao-pkg/pkg)(vercel/pkg 归档后 确定性协议实现(`server.ts` / `transport.ts`)按 `acp/acp` + `examples/acp-demo` 的既有模式落为两包——对外服务接口本身也是插件: -- [`packages/ui/jsonrpc`](../../../../packages/ui/jsonrpc/README.md)(`@deepseek-ai/dsh-jsonrpc`):纯协议插件;执行 `apply` 时,在进程 stdio 上挂载 `HarnessSdkServer` 与按行传输的 JSON-RPC 层,资源释放走 `ctx.effect()`。是否提供服务由 `cordis.yml` 决定;未挂载该插件的配置会启动一个不提供此服务的合法进程。协议级退出归插件所有(应答并刷新 `shutdown` 响应后 dispose 根运行时以排空持久化,再调用 `exit(0)`;HMR 式卸载只停止服务,不退出进程)。 -- [`packages/examples/jsonrpc-demo`](../../../../packages/examples/jsonrpc-demo/README.md)(`@deepseek-ai/dsh-jsonrpc-demo`):轻量应用入口——`installFailLoud` + `loadEnv` + 配置发现 + [`dsh-app-boot`](../../../../packages/ui/app-boot/src/index.ts) 的 `boot()`;`boot()` 完成后入口即完成,服务器由 `cordis.yml` 中的 `dsh-jsonrpc` 条目启动。它只依赖 `app-boot`。进程级退出归 `bin` 所有(stdin EOF/SIGTERM → dispose 后返回 0,SIGINT → 130)。 +- [`packages/scaffold/server`](../../../../packages/scaffold/server/README.md)(`@deepseek-ai/dsh-jsonrpc`):纯协议插件;执行 `apply` 时,在进程 stdio 上挂载 `HarnessSdkServer` 与按行传输的 JSON-RPC 层,资源释放走 `ctx.effect()`。是否提供服务由 `cordis.yml` 决定;未挂载该插件的配置会启动一个不提供此服务的合法进程。协议级退出归插件所有(应答并刷新 `shutdown` 响应后 dispose 根运行时以排空持久化,再调用 `exit(0)`;HMR 式卸载只停止服务,不退出进程)。 +- [`packages/examples/jsonrpc-demo`](../../../../packages/examples/jsonrpc-demo/README.md)(`@deepseek-ai/dsh-jsonrpc-demo`):轻量应用入口——`installFailLoud` + `loadEnv` + 配置发现 + [`dsh-app-boot`](../../../../packages/boot/app-boot/src/index.ts) 的 `boot()`;`boot()` 完成后入口即完成,服务器由 `cordis.yml` 中的 `dsh-jsonrpc` 条目启动。它只依赖 `app-boot`。进程级退出归 `bin` 所有(stdin EOF/SIGTERM → dispose 后返回 0,SIGINT → 130)。 配置发现有两个通道,均缺失时立即报错:优先使用 `DSH_CORDIS_CONFIG` 环境变量(SDK 客户端约定),其次使用 argv 位置参数;没有默认路径或内置回退——「实际启动的插件由外部 `cordis.yml` 决定」是硬语义。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.i18n.yaml index 6f4a6d363d..6e2c9a635d 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md -2026-07-20-config-hot-reload-resilience.md: 0f15bb0aaacb6e06c416cbe35b44155279497eee -2026-07-20-config-hot-reload-resilience.zh.md: 8a185c1915b5247150d8bb1dd5c42d69bd4f2a35 +2026-07-20-config-hot-reload-resilience.md: 2b98ee73e1c7734da9c0ae6df5e3de1af6a5cbeb +2026-07-20-config-hot-reload-resilience.zh.md: 4ddf1f07f4ff5449f3bd91aa2b38f6045eb4e1c6 diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md index 0f15bb0aaa..2b98ee73e1 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md +++ b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.md @@ -38,4 +38,4 @@ HMR contains live refresh rejection. Its `registerConfig(filename, refresh)` met ## Testing -`packages/ui/app-boot/tests/config-reload.spec.ts` boots real temporary Loader/Include trees and covers parse and shape rejection, import-before-dispose, plugin/config restoration, multi-entry rollback, ancestor disablement, overlay convergence, option identity, failed direct-update persistence, and failed programmatic moves. `packages/ui/app-boot/tests/hmr-config.spec.ts` covers existing and missing exact paths, add/change/removal, serialized coalescing, disposal drainage, non-`Error` normalization, failure broadcast, and rejecting-observer containment. `packages/host/webserver/tests/webserver.spec.ts` proves a service-gated startup failure rejects Loader composition with its bind diagnostic, `packages/typert/loader/tests/loader.spec.ts` exercises awaited programmatic removal through a real Loader consumer, and the ACP `pty-tools` snapshot guards concurrent composition from reordering equal-priority prompt sections. +`packages/boot/app-boot/tests/config-reload.spec.ts` boots real temporary Loader/Include trees and covers parse and shape rejection, import-before-dispose, plugin/config restoration, multi-entry rollback, ancestor disablement, overlay convergence, option identity, failed direct-update persistence, and failed programmatic moves. `packages/boot/app-boot/tests/hmr-config.spec.ts` covers existing and missing exact paths, add/change/removal, serialized coalescing, disposal drainage, non-`Error` normalization, failure broadcast, and rejecting-observer containment. `packages/host/webserver/tests/webserver.spec.ts` proves a service-gated startup failure rejects Loader composition with its bind diagnostic, `packages/typert/loader/tests/loader.spec.ts` exercises awaited programmatic removal through a real Loader consumer, and the ACP `pty-tools` snapshot guards concurrent composition from reordering equal-priority prompt sections. diff --git a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.zh.md b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.zh.md index 8a185c1915..4ddf1f07f4 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-20-config-hot-reload-resilience.zh.md @@ -38,4 +38,4 @@ HMR 收容实时刷新 rejection。其 `registerConfig(filename, refresh)` 方 ## Testing -`packages/ui/app-boot/tests/config-reload.spec.ts` 启动真实的临时 Loader/Include 树,并覆盖对解析和形状错误的拒绝、先导入再 dispose、插件/配置恢复、多配置项回滚、祖先禁用、overlay 收敛、option 对象身份、失败的直接更新不持久化以及失败的程序化移动。`packages/ui/app-boot/tests/hmr-config.spec.ts` 覆盖现有和缺失的确切路径、添加/变更/移除、串行化合并、dispose 排空、非 `Error` 值的规范化、失败广播以及对发生 rejection 的观察者的收容。`packages/host/webserver/tests/webserver.spec.ts` 证明受服务门控的启动失败会让 Loader 组合以其 bind 诊断 reject;`packages/typert/loader/tests/loader.spec.ts` 则通过真实 Loader 消费方演练可等待的程序化移除;ACP(Agent Client Protocol)的 `pty-tools` 快照会防止并发组合改变同优先级提示词段的顺序。 +`packages/boot/app-boot/tests/config-reload.spec.ts` 启动真实的临时 Loader/Include 树,并覆盖对解析和形状错误的拒绝、先导入再 dispose、插件/配置恢复、多配置项回滚、祖先禁用、overlay 收敛、option 对象身份、失败的直接更新不持久化以及失败的程序化移动。`packages/boot/app-boot/tests/hmr-config.spec.ts` 覆盖现有和缺失的确切路径、添加/变更/移除、串行化合并、dispose 排空、非 `Error` 值的规范化、失败广播以及对发生 rejection 的观察者的收容。`packages/host/webserver/tests/webserver.spec.ts` 证明受服务门控的启动失败会让 Loader 组合以其 bind 诊断 reject;`packages/typert/loader/tests/loader.spec.ts` 则通过真实 Loader 消费方演练可等待的程序化移除;ACP(Agent Client Protocol)的 `pty-tools` 快照会防止并发组合改变同优先级提示词段的顺序。 diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml index fa097ae230..b5c6343382 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-06-approval-seam.md -2026-07-06-approval-seam.md: ae143a41302b7bcd6029345fa91ca4eda837c141 -2026-07-06-approval-seam.zh.md: 2f95f67ecd33b4c659e56d5f3cc6168bf01e35ce +2026-07-06-approval-seam.md: a286e07d51a70232f9aab0af5d8a19622cd222d4 +2026-07-06-approval-seam.zh.md: 6656cfe46de663f0e89eacc78dcfb3425812f9e0 diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md index ae143a4130..a286e07d51 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.md @@ -12,7 +12,7 @@ The routing problem is ownership: a permission request must reach the channel th ## Decision -One package, `dsh-user-approval` (`packages/ui/user-approval`), owns the vocabulary and the `ctx.approval` service — the mechanism. The policy — who answers, and whether a session is asked at all — lives outside it: answerers are `approval/request` waterfall listeners registered by channel-owning plugins (the ACP bridge, host adapters, and test scripts), and a per-session policy tier can decide before a channel is involved. Consumers (`dsh-tools`' ask routing and the sandbox escalation gate) resolve a question to a closed outcome and derive their own tool results from it. This is deliberately one package, not the capability-seam three (see Alternatives). +One package, `dsh-user-approval` (`packages/interaction/user-approval`), owns the vocabulary and the `ctx.approval` service — the mechanism. The policy — who answers, and whether a session is asked at all — lives outside it: answerers are `approval/request` waterfall listeners registered by channel-owning plugins (the ACP bridge, host adapters, and test scripts), and a per-session policy tier can decide before a channel is involved. Consumers (`dsh-tools`' ask routing and the sandbox escalation gate) resolve a question to a closed outcome and derive their own tool results from it. This is deliberately one package, not the capability-seam three (see Alternatives). ### How a deployment uses it @@ -95,7 +95,7 @@ Snapshots record allowed and rejected sandbox escalation through `session/reques - **A single registered provider instead of waterfall listeners** — rejected: a `registerProvider()` surface forces every composition question — allowlist pre-filters, external hook deciders, scripted test answers, a policy gate in front of a human — inside one provider implementation. The waterfall gets composition, fail-closed absence, and HMR disposal from machinery the runtime already has; the seam's JSDoc pins the single-decision-slot convention instead of inventing a provider registry. - **An inline `tools/pre-execute` permission gate in the ACP bridge** — rejected: prompting for every bridge-owned call hardwires the asking policy into the transport, cannot serve a second asker (sandbox escalation happens after execution starts, with no pre-execute moment), and leaves hook-produced `ask` decisions without a shared mechanism. -- **The generic user-interaction seam (`ctx.userInteraction`)** — rejected as the approval mechanism: the two share a skeleton (route by agent, block for a human, handle absence), but approval's contract is narrower in every dimension that matters: a closed outcome vocabulary instead of free text, a protocol-native prompt attached to a tool call instead of a generic form, mandatory fail-closed absence, and audit events. Approval therefore does not ride the shipped `packages/ui/user-interaction` / `ask_user_question` elicitation path — an elicitation form is not a permission prompt, and a free-text answer is not a closed outcome; sharing provider plumbing stays open if the two ever converge. +- **The generic user-interaction seam (`ctx.userInteraction`)** — rejected as the approval mechanism: the two share a skeleton (route by agent, block for a human, handle absence), but approval's contract is narrower in every dimension that matters: a closed outcome vocabulary instead of free text, a protocol-native prompt attached to a tool call instead of a generic form, mandatory fail-closed absence, and audit events. Approval therefore does not ride the shipped `packages/interaction/user-interaction` / `ask_user_question` elicitation path — an elicitation form is not a permission prompt, and a free-text answer is not a closed outcome; sharing provider plumbing stays open if the two ever converge. - **Static optional injection in `dsh-tools`** — rejected: the vendored cordis `Inject` type has no optional flag — the object form maps service names to intercept config, and a declared inject gates the fiber. `ctx.get('approval')` is the documented opportunistic-consumption pattern (the `tool-bash` owner-token lookup, the loop's persistence probe), reads presence per call, and degrades correctly across HMR without extra machinery. - **The capability-seam three-package split** — rejected: interface/implementation/consumer fits a seam whose implementation is swappable (bash-local vs bash-sandbox). Here the service body is fixed mechanism and the variable part is listeners that live with their owners — splitting would manufacture an implementation package with nothing in it ("don't split preemptively"). - **Offering `allow_always` now** — rejected: the protocol can express it, but honoring it means designing grant storage, scope identity, and revocation (§ Deferred). Advertising an option the harness cannot honor manufactures doomed grants. diff --git a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md index 2f95f67ecd..6656cfe46d 100644 --- a/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -一个包`dsh-user-approval`(`packages/ui/user-approval`)负责定义词汇表和 `ctx.approval` 服务——即机制。策略——谁来应答、某个会话是否需要被询问——不在其中:应答者是 `approval/request` waterfall 监听器,由拥有通道的插件注册(ACP(Agent Client Protocol)桥、宿主适配器、测试脚本),而每会话的策略层可以在任何通道介入之前做出决定。消费方(`dsh-tools` 的 ask 路由和沙箱升级门禁)将问题解析为一个封闭结果,并从中派生各自的工具结果。刻意设计为一个包,而非能力 seam 的三包拆分(见「替代方案」)。 +一个包`dsh-user-approval`(`packages/interaction/user-approval`)负责定义词汇表和 `ctx.approval` 服务——即机制。策略——谁来应答、某个会话是否需要被询问——不在其中:应答者是 `approval/request` waterfall 监听器,由拥有通道的插件注册(ACP(Agent Client Protocol)桥、宿主适配器、测试脚本),而每会话的策略层可以在任何通道介入之前做出决定。消费方(`dsh-tools` 的 ask 路由和沙箱升级门禁)将问题解析为一个封闭结果,并从中派生各自的工具结果。刻意设计为一个包,而非能力 seam 的三包拆分(见「替代方案」)。 ### 部署如何使用它 @@ -95,7 +95,7 @@ ACP 桥只应答其会话映射所拥有的精确 agent 对象。它携带既有 - **单一注册提供方而非 waterfall 监听器**:否决。`registerProvider()` 接口迫使所有组合问题——允许列表预过滤、外部钩子决策者、脚本化测试应答、人类前面的策略门禁——都塞进一个提供方实现。waterfall 直接复用运行时已有的组合能力、缺失时默认拒绝行为和 HMR(热模块替换)资源释放机制;seam 的 JSDoc 以约定固定单决策槽语义,而非发明一个提供方注册表。 - **在 ACP 桥中内联 `tools/pre-execute` 权限门禁**:否决。对桥拥有的每次调用都弹出提示,会将请求策略硬编码进传输层,无法服务第二个发起方(沙箱升级发生在执行开始之后,没有 pre-execute 时刻),且钩子产生的 `ask` 决策没有共享机制。 -- **通用用户交互 seam(`ctx.userInteraction`)**:否决作为审批机制。二者骨架相似(按 agent 路由、阻塞等待人类、处理缺失),但审批的契约在每个关键维度上都更窄:封闭的结果词汇而非自由文本、附着在工具调用上的协议原生提示而非通用表单、强制的缺失时失败关闭、以及审计事件。因此审批不走已交付的 `packages/ui/user-interaction` / `ask_user_question` 信息征集路径——信息征集表单不是权限提示,自由文本应答不是封闭结果;如果二者将来趋同,共享提供方管道仍然开放。 +- **通用用户交互 seam(`ctx.userInteraction`)**:否决作为审批机制。二者骨架相似(按 agent 路由、阻塞等待人类、处理缺失),但审批的契约在每个关键维度上都更窄:封闭的结果词汇而非自由文本、附着在工具调用上的协议原生提示而非通用表单、强制的缺失时失败关闭、以及审计事件。因此审批不走已交付的 `packages/interaction/user-interaction` / `ask_user_question` 信息征集路径——信息征集表单不是权限提示,自由文本应答不是封闭结果;如果二者将来趋同,共享提供方管道仍然开放。 - **`dsh-tools` 中的静态可选注入**:否决。vendor 的 Cordis `Inject` 类型没有 optional 标志——对象形式将服务名映射到拦截配置,声明的 inject 会阻塞 fiber。`ctx.get('approval')` 是文档化的机会性消费模式(`tool-bash` 的 owner-token 查找、loop 的持久化探测),按调用读取存在性,跨 HMR 正确降级,无需额外机制。 - **能力 seam 的三包拆分**:否决。接口/实现/消费方适合实现可替换的 seam(bash-local vs bash-sandbox)。此处服务体是固定机制,可变部分是留在各自通道拥有者插件中的监听器——拆分只会制造一个空的实现包(「不要预防性拆分」)。 - **现在就提供 `allow_always`**:否决。协议能表达它,但兑现它意味着设计授权存储、作用域标识和撤销(§ 延后)。展示 harness 无法兑现的选项只会制造注定失败的授权。 diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml index 205ce40bb2..b2e1785796 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md -2026-07-12-subagent-persona-tool-filter-and-depth.md: c690f4701a54272205eedf719fbfe0863bac566c -2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: e14214b16c413eaa05bcea7bcdf9bca996e8d616 +2026-07-12-subagent-persona-tool-filter-and-depth.md: 2c5eaf8829e01536a2de34cc190573ff35e4ab77 +2026-07-12-subagent-persona-tool-filter-and-depth.zh.md: 355db26ecaf57137420798474561d6a7032757cb diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md index c690f4701a..2c5eaf8829 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md @@ -53,7 +53,7 @@ The depth limit bounds recursive delegation independently of tool visibility. A The effective parent depth is the greater of durable `SessionHeader.delegationDepth` and runtime `AgentOptions.subagentDepth`. An in-process child records its derived depth in the session header, and resume restores that header, so a restart cannot lower the recursion count. -Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; loader-resolved `dsh-tool-subagent` configuration instead defaults to `3`, accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. Three is a small finite default that still permits a root plus three descendant generations: the [SDK helper's generated subagent entries](../../../../packages/sdk/helper/src/features/builtin/index.ts) and [JSON-RPC example](../../../../examples/jsonrpc-agent/cordis.yml) use that general policy, while the shipped interactive ACP, headless, and REPL examples pin one. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`. +Every public entry validates the domain rather than relying on one model-facing configuration path. Negative values, fractions, negative zero, non-finite values, unsafe integers, malformed stored parent depth, and derived overflow all reject. A direct `SubagentStartRequest` may omit the cap to leave depth unbounded; loader-resolved `dsh-tool-subagent` configuration instead defaults to `3`, accepts a numeric override, and uses explicit `'provider-managed'` to omit the cap for an out-of-process provider whose deployment owns its recursion budget. Three is a small finite default that still permits a root plus three descendant generations: the [SDK helper's generated subagent entries](../../../../packages/scaffold/helper/src/features/builtin/index.ts) and [JSON-RPC example](../../../../examples/jsonrpc-agent/cordis.yml) use that general policy, while the shipped interactive ACP, headless, and REPL examples pin one. A numeric tool cap fails at provider mount when the provider lacks `depthLimit`. A deployment can combine depth and filtering, but the numeric cap does not synthesize a filter. The delegation tool stays visible at the cap because authorization may depend on runtime state; every attempted start checks the calling agent's current durable and runtime depth, and a rejected start returns an errored tool result without publishing a child. A deployment may separately deny delegation tools in children when its visibility policy is static. Neither choice changes the provider's conversation-history behavior. diff --git a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md index e14214b16c..355db26eca 100644 --- a/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md +++ b/.agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.zh.md @@ -53,7 +53,7 @@ subagent 启动有三个独立的组合控制:`persona`、`toolFilter` 和 `ma 有效父级深度取持久 `SessionHeader.delegationDepth` 与运行时 `AgentOptions.subagentDepth` 中的较大值。进程内子 agent 把推导出的深度记录在会话 header 中,恢复时会重新载入该 header,因此重启无法降低递归计数。 -每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;经 loader 解析的 `dsh-tool-subagent` 配置则默认值为 `3`、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。三是一个较小的有限默认值,仍允许 root 加三代后代:[SDK 辅助函数生成的 subagent 条目](../../../../packages/sdk/helper/src/features/builtin/index.ts)和 [JSON-RPC 示例](../../../../examples/jsonrpc-agent/cordis.yml)采用这项通用策略,而已交付的交互式 ACP、headless 和 REPL 示例固定为一。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。 +每个公开入口都自行验证值域,而非依赖单一的面向模型配置路径。负值、小数、负零、非有限值、不安全整数、格式错误的存储父级深度以及推导溢出均被拒绝。直接的 `SubagentStartRequest` 可以省略上限,让此机制不约束深度;经 loader 解析的 `dsh-tool-subagent` 配置则默认值为 `3`、接受数值覆盖,并使用显式的 `'provider-managed'` 来省略由进程外提供方部署拥有递归预算时的上限。三是一个较小的有限默认值,仍允许 root 加三代后代:[SDK 辅助函数生成的 subagent 条目](../../../../packages/scaffold/helper/src/features/builtin/index.ts)和 [JSON-RPC 示例](../../../../examples/jsonrpc-agent/cordis.yml)采用这项通用策略,而已交付的交互式 ACP、headless 和 REPL 示例固定为一。提供方缺少 `depthLimit` 时,数值工具上限会在提供方挂载阶段失败。 部署可以组合深度与过滤,但数值上限不会合成过滤器。委派工具在上限处仍然可见,因为授权可能依赖运行时状态;每次尝试启动都会检查调用方 agent 当前的持久与运行时深度,被拒绝的启动返回错误工具结果,且不发布子 agent。可见性策略固定的部署可以另外在子 agent 中 deny 委派工具。两种选择都不改变提供方的对话历史行为。 diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml index 673e4869af..a54f2e9496 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-16-harness-level-loop.md -2026-07-16-harness-level-loop.md: 36a567204ee1082d48126369ee0b7277c9f24ca8 -2026-07-16-harness-level-loop.zh.md: cb8d29b4c77848613a6718ea3a343b0ee3294ea4 +2026-07-16-harness-level-loop.md: f37bea0842b3f40bf07c6660ad758d84ece23f1b +2026-07-16-harness-level-loop.zh.md: 0ec2dc2294c1adf8b7f6f810bfaffa7e08c19ed2 diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md index 36a567204e..f37bea0842 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.md @@ -38,7 +38,7 @@ Time-based `/loop` or scheduled execution is a third policy and is not implement | `@deepseek-ai/dsh-goal` | `packages/goal/goal/`, domain service | Owns `GoalId`, compare-and-set `GoalRef`, `GoalSnapshot`, four-state `GoalPhase`, structured `GoalBlockReason`, process-local `GoalActivation`, replay folding, and `get`, `create`, `edit`, `pause`, `resume`, `complete`, `block`, `clear`, and `disarm` verbs. | | `@deepseek-ai/dsh-tool-goal` | `packages/goal/tool-goal/`, model-facing consumer | Registers exclusive `get_goal`, `create_goal`, and `update_goal`; authenticates live turn provenance and narrows autonomous-round authority to completion or blocking reports with machine-routable reason codes. | | `@deepseek-ai/dsh-goal-session` | `packages/goal/goal-session/`, continuation policy | Reserves, fences, admits, attributes, settles, cancels, and quiescently drains same-session goal rounds without importing the concrete loop. | -| `@deepseek-ai/dsh-commands` | `packages/ui/commands/`, UI registry | Owns `CommandDefinition`, discovery, scoped registration, direct dispatch, `CommandResult`, and request cancellation for human-only commands. | +| `@deepseek-ai/dsh-commands` | `packages/interaction/commands/`, UI registry | Owns `CommandDefinition`, discovery, scoped registration, direct dispatch, `CommandResult`, and request cancellation for human-only commands. | | `@deepseek-ai/dsh-command-goal` | `packages/goal/command-goal/`, human-command producer | Registers `/goal` status, creation, edit, pause, resume, and clear over the goal domain for TUI. | | `@deepseek-ai/dsh-tool-ralph` | `packages/workflow/tool-ralph/`, fixed workflow consumer | Registers `ralph({ objective, maxRounds? })`, validates the fresh structured provider and bounded `RalphRoundReport`, and returns `complete`, `blocked`, or `budget-limited`. | diff --git a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md index cb8d29b4c7..0ec2dc2294 100644 --- a/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md +++ b/.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md @@ -38,7 +38,7 @@ Status: implemented | `@deepseek-ai/dsh-goal` | `packages/goal/goal/`,领域服务 | 拥有 `GoalId`、比较并交换 `GoalRef`、`GoalSnapshot`、四状态 `GoalPhase`、结构化 `GoalBlockReason`、进程本地 `GoalActivation`、重放折叠,以及 `get`、`create`、`edit`、`pause`、`resume`、`complete`、`block`、`clear` 与 `disarm` 动词。 | | `@deepseek-ai/dsh-tool-goal` | `packages/goal/tool-goal/`,面向模型消费方 | 注册互斥的 `get_goal`、`create_goal` 与 `update_goal`;认证实时 Turn 来源,并把自治 Round 权限收窄到带机器可路由原因代码的完成或阻塞报告。 | | `@deepseek-ai/dsh-goal-session` | `packages/goal/goal-session/`,续行策略 | 在不导入具体 loop 的情况下,预留、设围栏、接纳、归属、结算、取消并排空同会话 Goal Round,直至完全停稳。 | -| `@deepseek-ai/dsh-commands` | `packages/ui/commands/`,UI 注册表 | 拥有面向人类专用命令的 `CommandDefinition`、发现、作用域注册、直接分发、`CommandResult` 与请求取消。 | +| `@deepseek-ai/dsh-commands` | `packages/interaction/commands/`,UI 注册表 | 拥有面向人类专用命令的 `CommandDefinition`、发现、作用域注册、直接分发、`CommandResult` 与请求取消。 | | `@deepseek-ai/dsh-command-goal` | `packages/goal/command-goal/`,人类命令生产方 | 为 TUI 注册构建在目标领域之上的 `/goal` 状态、创建、编辑、暂停、恢复与清除。 | | `@deepseek-ai/dsh-tool-ralph` | `packages/workflow/tool-ralph/`,固定工作流消费方 | 注册 `ralph({ objective, maxRounds? })`,验证全新结构化 provider 与有界 `RalphRoundReport`,并返回 `complete`、`blocked` 或 `budget-limited`。 | diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml index c3bbbe1d22..e58d618cf3 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md -2026-07-19-plugin-command-registration.md: 5233ce511dc9798733513ccbf6824f3e1b68d2e6 -2026-07-19-plugin-command-registration.zh.md: 7f41b9d2d00373901b51e3338fa51e1cadf886a9 +2026-07-19-plugin-command-registration.md: c8f0f2772a41948e9eb257a16f40194518c568f9 +2026-07-19-plugin-command-registration.zh.md: 94853393f5f71b29cfbd8da5443b3d73ded20e92 diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md index 5233ce511d..c8f0f2772a 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md @@ -12,7 +12,7 @@ A shared mechanism must remain a UI concern rather than a model tool or agent-lo ## Decision -`@deepseek-ai/dsh-commands` in `packages/ui/commands/` is the product command registry. The TUI app bundle mounts it beside its consuming front door; the [automation-only ACP app](../simplification/2026-07-23-acp-automation-only-protocol.md) and the executor-less, UI-less agent spine omit it. TUI injects the service, while command producers depend only on the registry and any domain they operate. +`@deepseek-ai/dsh-commands` in `packages/interaction/commands/` is the product command registry. The TUI app bundle mounts it beside its consuming front door; the [automation-only ACP app](../simplification/2026-07-23-acp-automation-only-protocol.md) and the executor-less, UI-less agent spine omit it. TUI injects the service, while command producers depend only on the registry and any domain they operate. ### Registry contract diff --git a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md index 7f41b9d2d0..94853393f5 100644 --- a/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md +++ b/.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.zh.md @@ -12,7 +12,7 @@ TUI 拥有斜杠命令。如果命令名、帮助文本、自动补全、分派 ## 决策 -位于 `packages/ui/commands/` 的 `@deepseek-ai/dsh-commands` 是产品命令注册表。TUI 应用 bundle(组合包)把它挂载在消费该服务的入口旁;[仅面向自动化的 ACP(Agent Client Protocol)应用](../simplification/2026-07-23-acp-automation-only-protocol.md)和无执行器、无 UI 的智能体 spine(主干)都省略该服务。TUI 注入该服务,命令生产者只依赖注册表及其操作的领域。 +位于 `packages/interaction/commands/` 的 `@deepseek-ai/dsh-commands` 是产品命令注册表。TUI 应用 bundle(组合包)把它挂载在消费该服务的入口旁;[仅面向自动化的 ACP(Agent Client Protocol)应用](../simplification/2026-07-23-acp-automation-only-protocol.md)和无执行器、无 UI 的智能体 spine(主干)都省略该服务。TUI 注入该服务,命令生产者只依赖注册表及其操作的领域。 ### 注册表契约 diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml index ee99911ee8..0cf926921b 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md -2026-07-20-dsh-cli-personal-config.md: 2a8ae4b235823b4493d2f082d37b85806f45b662 -2026-07-20-dsh-cli-personal-config.zh.md: d8ff6c4fcc5da8f1db6f030e990118e30ae6fe41 +2026-07-20-dsh-cli-personal-config.md: c00432ba2b75cc42caf23e405c24acb33d839e7a +2026-07-20-dsh-cli-personal-config.zh.md: b33a7fa321ca04f41e47386544ffc0f268a8b1b6 diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md index 2a8ae4b235..c00432ba2b 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md @@ -48,4 +48,4 @@ The TUI and Web register the exact personal path through Cordis HMR after boot. ## Testing -`packages/ui/app-boot/tests/user-patches.spec.ts` pins parsing, startup application, exact-path add/failure/recovery/removal, last-good rollback, failure broadcast, and preservation of app-owned patches. `apps/cli/tests/built-bin.e2e.ts` boots the real dsh bin over a profile and exercises the live patch layer end to end. Test launchers isolate `$DSH_HOME`, so a developer's real overlay cannot leak into fixtures. +`packages/boot/app-boot/tests/user-patches.spec.ts` pins parsing, startup application, exact-path add/failure/recovery/removal, last-good rollback, failure broadcast, and preservation of app-owned patches. `apps/cli/tests/built-bin.e2e.ts` boots the real dsh bin over a profile and exercises the live patch layer end to end. Test launchers isolate `$DSH_HOME`, so a developer's real overlay cannot leak into fixtures. diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md index d8ff6c4fcc..b33a7fa321 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md @@ -48,4 +48,4 @@ TUI 和 Web 启动后通过 Cordis HMR(热模块替换)注册确切的个人 ## Testing -`packages/ui/app-boot/tests/user-patches.spec.ts` 固定解析、启动时应用、确切路径的新增/失败/恢复/移除、最后可用状态回滚、失败广播以及应用自有 patch 的保留。`apps/cli/tests/built-bin.e2e.ts` 启动真实 dsh bin 并基于 profile 端到端验证实时 patch 层。测试启动器会隔离 `$DSH_HOME`,因此开发者的真实 overlay 不会泄漏进 fixture。 +`packages/boot/app-boot/tests/user-patches.spec.ts` 固定解析、启动时应用、确切路径的新增/失败/恢复/移除、最后可用状态回滚、失败广播以及应用自有 patch 的保留。`apps/cli/tests/built-bin.e2e.ts` 启动真实 dsh bin 并基于 profile 端到端验证实时 patch 层。测试启动器会隔离 `$DSH_HOME`,因此开发者的真实 overlay 不会泄漏进 fixture。 diff --git a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.i18n.yaml index a7d065d42d..1d93b7f440 100644 --- a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md -2026-07-21-continuable-background-subagents.md: 6bbbfb25fb24eba95de888c9d23c79e24fc8535b -2026-07-21-continuable-background-subagents.zh.md: e23fbe67cc2d16a2c01a1b7ed4884008f8932dd4 +2026-07-21-continuable-background-subagents.md: e37abdd798242bc1086754e86a85ef46c08e59a3 +2026-07-21-continuable-background-subagents.zh.md: 799e1914bdeb571dc697652081be6b0170768655 diff --git a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md index 6bbbfb25fb..e37abdd798 100644 --- a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md +++ b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md @@ -112,7 +112,7 @@ Task records and active-run associations are process-local. Persistence makes th - `packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts` pins the continuable durability boundary: an absent or detached flush listener and a permanent listener failure reject with `DURABILITY_FAILED`, a transient loop-checkpoint failure can succeed on the final confirmation, cancellation owns either final-checkpoint outcome, resume also confirms durability, and foreground runs remain best-effort. `packages/subagent/subagent/tests/continuation.spec.ts` drives the real stack (agent loop, JSONL persistence, spawn/fork providers, Task service, and `ctx.subagents`) keylessly: initial and resumed activations create fresh Tasks and dispose their runs before terminal; the descriptor event is pre-turn, model-hidden, versioned, durable under the service-allocated child id, and survives blocked or throwing initial prompt admission; cancellation, steering, cold follow-up, authorization, ownership conflicts, and resume races retain the contracts described above. - `packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts` pins the `send_message` schema, coordinator attribution, both route renderings, the not-delivered failure, the no-agent rejection, and HMR disposal. - `packages/subagent/tool-subagent/tests/tool-subagent.spec.ts` covers the configured background route: continuable mode requires a resumable provider and returns both ids without requiring `send_message`, while one-shot mode keeps the plain Task acknowledgement even when the provider can resume. -- `packages/sdk/helper/tests/project.spec.ts` pins the Task service and model-facing Task controls in generated spawn and fork compositions. +- `packages/scaffold/helper/tests/project.spec.ts` pins the Task service and model-facing Task controls in generated spawn and fork compositions. - The keyless ACP snapshot scenario `subagent-continuable` (examples/acp-agent) pins the model-visible transcript: the two-id acknowledgement, a final durability-confirmation failure rendered through `task_output` without unconfirmed child output, and a `send_message` follow-up whose started Task fails with the id unavailable. ## Consequences diff --git a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.zh.md b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.zh.md index e23fbe67cc..799e1914bd 100644 --- a/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.zh.md +++ b/.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.zh.md @@ -112,7 +112,7 @@ Task 记录和活跃 run 关联都位于进程内。持久化使 child 会话可 - `packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts` 固定可继续执行的持久性边界:缺少 flush 监听器、flush 监听器已脱离或监听器持续失败时,均会以 `DURABILITY_FAILED` 拒绝;循环检查点的瞬时失败可在最终确认成功后继续完成,发生取消时最终检查点无论成功还是失败都由取消优先决定结果,resume 同样会确认持久性,而前台运行仍采用尽力而为策略。`packages/subagent/subagent/tests/continuation.spec.ts` 以无密钥方式驱动真实栈(agent loop、JSONL 持久化、spawn/fork 提供方、Task 服务和 `ctx.subagents`):初始及恢复后的激活都会创建新 Task,并在进入终态前 dispose 各自的 run;描述符事件位于轮次前、对模型隐藏、带版本、在服务分配的 child id 下持久化,并在初始 prompt admission 阻止请求或抛出异常时仍保留;取消、steering、cold follow-up、授权、所有权冲突与 resume 竞态保留上述契约。 - `packages/subagent/tool-subagent-control/tests/tool-subagent-control.spec.ts` 固定 `send_message` 的 schema、coordinator 来源标记、两种路由渲染、未送达失败、无 agent 时的拒绝,以及 HMR(热模块替换)dispose。 - `packages/subagent/tool-subagent/tests/tool-subagent.spec.ts` 覆盖配置的后台路由:可继续模式要求提供方可恢复,并在不要求 `send_message` 的情况下返回两个 id;即使提供方可以恢复,一次性模式仍保持普通的 Task 确认消息。 -- `packages/sdk/helper/tests/project.spec.ts` 固定生成的 spawn 与 fork 组合中的 Task 服务及面向模型的 Task 控制工具。 +- `packages/scaffold/helper/tests/project.spec.ts` 固定生成的 spawn 与 fork 组合中的 Task 服务及面向模型的 Task 控制工具。 - 无密钥 ACP 快照场景 `subagent-continuable`(examples/acp-agent)固定模型可见的 transcript:双 id 确认消息、最终持久性确认失败(该失败通过 `task_output` 呈现,且不包含未经确认的 child 输出),以及一次 `send_message` 后续操作——其已启动的 Task 会带着「id 不可用」失败。 ## 影响 diff --git a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.i18n.yaml b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.i18n.yaml index 6018cb1305..38dc165e7f 100644 --- a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md -2026-07-27-typescript-sdk-and-sdk-subagent-backend.md: 96ffd772810a7908ff452967aa0be0e540bc2d8c -2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md: 74ae4b81807d1267dc19f30d2bb51e50a775c4be +2026-07-27-typescript-sdk-and-sdk-subagent-backend.md: c5c6c4eac053e9a3adfa2a2a441ac492ba3391ac +2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md: 643ec36134a1ebe940da5de5b373fa0c25afcd42 diff --git a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md index 96ffd77281..c5c6c4eac0 100644 --- a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md +++ b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md @@ -12,8 +12,8 @@ The stdio JSON-RPC serving surface (`@deepseek-ai/dsh-jsonrpc`, the [single-exe Three packages, layered exactly like the existing Python stack, plus one seam registration: -- **`@deepseek-ai/dsh-sdk-protocol`** (`packages/sdk/sdk-protocol/`) — the wire made shared and nominal. `JsonRpcLineTransport` moves here verbatim from `dsh-jsonrpc` (which now imports it), and `types.ts` names every payload the server speaks: `InitializeParams/Result`, `SessionPromptParams/Result`, the four notification payloads, and the `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` indexes. The package root explicitly exports that complete interface and provides no source-module deep imports. The server's `notify()` call sites are typed against these named payloads, so server drift breaks compilation, not clients. One behavioral change: an error response now rejects with `JsonRpcResponseError` carrying the wire `code`/`data` (the Python client already preserved these; the old transport threw a bare `Error` with only the message). -- **`@deepseek-ai/dsh-sdk-client`** (`packages/sdk/sdk-client/`) — the TypeScript twin of `python/sdk`: `HarnessClient` (spawn, frame, fan out notifications, typed error surfaces, close-to-quiescence via the shared dispose ladder) under `DeepSeekHarness`/`HarnessSession` (lazy start, memoized `initialize`, `run()` pairing one `session/prompt` with its `session.finished`). Its package-root consumer interface explicitly exports both client layers, caller-facing types, and the protocol-owned `JsonRpcResponseError`; source modules, normalization helpers, and the notification producer stay internal. `TurnResult.events` contains only the root session's typed events, while `notifications` retains session ids across the root and descendants discovered from `subagent.started`; session-tree scoping is client-side, mirroring `client.py`. Deliberate asymmetries with Python: the launch spec is explicit `command`/`args` (no bundled-runtime resolution — that is a distribution concern with no TS consumer yet); `env` replaces rather than merges (callers own credential policy; `scrubbedParentEnv` from the subprocess seam is one import away); `TurnResult` carries the structured `reason` (Python exposes only `status`); teardown walks a private stdin-EOF → SIGTERM → SIGKILL ladder to actual exit (the client runs outside any harness context, so it cannot ride `ctx.subprocess`). +- **`@deepseek-ai/dsh-sdk-protocol`** (`packages/scaffold/protocol/`) — the wire made shared and nominal. `JsonRpcLineTransport` moves here verbatim from `dsh-jsonrpc` (which now imports it), and `types.ts` names every payload the server speaks: `InitializeParams/Result`, `SessionPromptParams/Result`, the four notification payloads, and the `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` indexes. The package root explicitly exports that complete interface and provides no source-module deep imports. The server's `notify()` call sites are typed against these named payloads, so server drift breaks compilation, not clients. One behavioral change: an error response now rejects with `JsonRpcResponseError` carrying the wire `code`/`data` (the Python client already preserved these; the old transport threw a bare `Error` with only the message). +- **`@deepseek-ai/dsh-sdk-client`** (`packages/scaffold/client/`) — the TypeScript twin of `python/sdk`: `HarnessClient` (spawn, frame, fan out notifications, typed error surfaces, close-to-quiescence via the shared dispose ladder) under `DeepSeekHarness`/`HarnessSession` (lazy start, memoized `initialize`, `run()` pairing one `session/prompt` with its `session.finished`). Its package-root consumer interface explicitly exports both client layers, caller-facing types, and the protocol-owned `JsonRpcResponseError`; source modules, normalization helpers, and the notification producer stay internal. `TurnResult.events` contains only the root session's typed events, while `notifications` retains session ids across the root and descendants discovered from `subagent.started`; session-tree scoping is client-side, mirroring `client.py`. Deliberate asymmetries with Python: the launch spec is explicit `command`/`args` (no bundled-runtime resolution — that is a distribution concern with no TS consumer yet); `env` replaces rather than merges (callers own credential policy; `scrubbedParentEnv` from the subprocess seam is one import away); `TurnResult` carries the structured `reason` (Python exposes only `status`); teardown walks a private stdin-EOF → SIGTERM → SIGKILL ladder to actual exit (the client runs outside any harness context, so it cannot ride `ctx.subprocess`). - **`@deepseek-ai/dsh-subagent-dsh-sdk`** (`packages/subagent/subagent-dsh-sdk/`) — the second out-of-process `SubagentProvider`, structured as `subagent-acp`'s sibling: same all-false capabilities and `inheritsParentContext: false`, same publish-after-handshake ownership transaction, same result-never-rejects flattening through an `onError` sink, same parent-namespace run id. The child answer is read from streamed `session.event`s — the last complete `assistant/message`, else accumulated `text-delta` chunks, so partial answers survive cancellation. Stop reasons map from the child's structured `TurnEndReason` (`completed`/`max-tokens`/`aborted` pass through; everything else, including a settled-without-turn child, is `error`). Its `provider`/`model` config feeds the child's `initialize`; `env` is where deployments pass the child's own key and `DSH_CORDIS_CONFIG`. - **The subagent seam grows `out-of-process.ts`**: the provider-side vocabulary both out-of-process backends share — `NO_START_CAPABILITIES`, timing-bound validation, child cwd resolution (config override, else the delegating parent session's workspace), the never-reject `settleRunResult`, and the `subprocessRunHandle` publication. Process mechanics (spawn, env scrub, tree-scoped teardown) live in the `dsh-subprocess` seam; `subagent-acp` spawns through `ctx.subprocess`, while this backend spawns through the SDK client (the subprocess README's documented exception for SDK-managed transports) and applies the seam's `scrubbedParentEnv()` itself. diff --git a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md index 74ae4b8180..643ec36134 100644 --- a/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md +++ b/.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.zh.md @@ -12,8 +12,8 @@ stdio JSON-RPC 对外服务接口(`@deepseek-ai/dsh-jsonrpc`,见[单文件 三个包,分层与既有 Python 栈完全一致,外加一个 seam 注册: -- **`@deepseek-ai/dsh-sdk-protocol`**(`packages/sdk/sdk-protocol/`)—— 把线协议做成共享且具名。`JsonRpcLineTransport` 从 `dsh-jsonrpc` 原样移入(后者现在导入它),`types.ts` 为服务器所说的每个载荷命名:`InitializeParams/Result`、`SessionPromptParams/Result`、四个通知载荷,以及 `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` 索引。该包根显式导出这一完整接口,且不提供指向源模块的深层导入。服务器的 `notify()` 调用点以这些具名载荷标注类型,服务器漂移会先破坏编译而不是破坏客户端。一处行为变化:错误响应现在以携带线上 `code`/`data` 的 `JsonRpcResponseError` 拒绝(Python 客户端本就保留这些;旧传输只抛携带消息的裸 `Error`)。 -- **`@deepseek-ai/dsh-sdk-client`**(`packages/sdk/sdk-client/`)—— `python/sdk` 的 TypeScript 孪生:`HarnessClient`(spawn、分帧、通知扇出、有类型的错误表面、经共享 dispose(资源释放)阶梯关闭至完全停稳)之上是 `DeepSeekHarness`/`HarnessSession`(惰性启动、记忆化 `initialize`、`run()` 把一个 `session/prompt` 与其 `session.finished` 配对)。其包根消费方接口显式导出两层客户端、面向调用方的类型,以及协议包所拥有的 `JsonRpcResponseError`;源模块、规范化辅助函数和通知投递端都保留为内部实现。`TurnResult.events` 只包含根会话的类型化事件,而 `notifications` 则保留根会话及从 `subagent.started` 发现的后代各自的会话 id;基于 `subagent.started` 血缘边的会话树范围限定在客户端完成,镜像 `client.py`。与 Python 的刻意不对称:启动规格是显式 `command`/`args`(无捆绑运行时解析——那是尚无 TS 消费方的发行问题);`env` 整体替换而非合并(凭据策略归调用方;subprocess seam 的 `scrubbedParentEnv` 一个 import 即得);`TurnResult` 携带结构化 `reason`(Python 只暴露 `status`);拆除走私有的 stdin-EOF → SIGTERM → SIGKILL 阶梯直到真正退出(客户端运行在任何 harness 上下文之外,无法搭乘 `ctx.subprocess`)。 +- **`@deepseek-ai/dsh-sdk-protocol`**(`packages/scaffold/protocol/`)—— 把线协议做成共享且具名。`JsonRpcLineTransport` 从 `dsh-jsonrpc` 原样移入(后者现在导入它),`types.ts` 为服务器所说的每个载荷命名:`InitializeParams/Result`、`SessionPromptParams/Result`、四个通知载荷,以及 `HarnessSdkRequestMap`/`HarnessSdkNotificationMap` 索引。该包根显式导出这一完整接口,且不提供指向源模块的深层导入。服务器的 `notify()` 调用点以这些具名载荷标注类型,服务器漂移会先破坏编译而不是破坏客户端。一处行为变化:错误响应现在以携带线上 `code`/`data` 的 `JsonRpcResponseError` 拒绝(Python 客户端本就保留这些;旧传输只抛携带消息的裸 `Error`)。 +- **`@deepseek-ai/dsh-sdk-client`**(`packages/scaffold/client/`)—— `python/sdk` 的 TypeScript 孪生:`HarnessClient`(spawn、分帧、通知扇出、有类型的错误表面、经共享 dispose(资源释放)阶梯关闭至完全停稳)之上是 `DeepSeekHarness`/`HarnessSession`(惰性启动、记忆化 `initialize`、`run()` 把一个 `session/prompt` 与其 `session.finished` 配对)。其包根消费方接口显式导出两层客户端、面向调用方的类型,以及协议包所拥有的 `JsonRpcResponseError`;源模块、规范化辅助函数和通知投递端都保留为内部实现。`TurnResult.events` 只包含根会话的类型化事件,而 `notifications` 则保留根会话及从 `subagent.started` 发现的后代各自的会话 id;基于 `subagent.started` 血缘边的会话树范围限定在客户端完成,镜像 `client.py`。与 Python 的刻意不对称:启动规格是显式 `command`/`args`(无捆绑运行时解析——那是尚无 TS 消费方的发行问题);`env` 整体替换而非合并(凭据策略归调用方;subprocess seam 的 `scrubbedParentEnv` 一个 import 即得);`TurnResult` 携带结构化 `reason`(Python 只暴露 `status`);拆除走私有的 stdin-EOF → SIGTERM → SIGKILL 阶梯直到真正退出(客户端运行在任何 harness 上下文之外,无法搭乘 `ctx.subprocess`)。 - **`@deepseek-ai/dsh-subagent-dsh-sdk`**(`packages/subagent/subagent-dsh-sdk/`)—— 第二个进程外 `SubagentProvider`,采用与 `subagent-acp` 对等的结构:同样的全 false 能力与 `inheritsParentContext: false`,同样的握手后发布所有权事务,同样通过 `onError` sink 将结果归一为绝不拒绝,同样的父命名空间 run id。子答案从流式 `session.event` 读取——最后一条完整 `assistant/message`,否则累积的 `text-delta` 块,部分答案在取消时得以保留。停止原因由子进程的结构化 `TurnEndReason` 映射(`completed`/`max-tokens`/`aborted` 直通;其余一切、包括未运行任何轮次便已结束的子进程,都是 `error`)。其 `provider`/`model` 配置喂给子进程的 `initialize`;`env` 是部署传入子进程自有密钥与 `DSH_CORDIS_CONFIG` 的地方。 - **subagent seam 新增 `out-of-process.ts`**:两个进程外后端共享的 provider 侧词汇——`NO_START_CAPABILITIES`、时限校验、子进程 cwd 解析(配置覆盖、否则发起委托的父会话工作区)、绝不拒绝的 `settleRunResult`、以及 `subprocessRunHandle` 发布。进程机制(spawn、环境清理、进程树清理)属于 `dsh-subprocess` seam;`subagent-acp` 经 `ctx.subprocess` spawn 子进程,本后端则经 SDK 客户端 spawn 子进程(subprocess README 记载的 SDK 托管传输例外)并自行应用该 seam 的 `scrubbedParentEnv()`。 diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml index 7adc61691c..0fe388a3dc 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md -2026-07-27-session-projection-and-command-log.md: 8226325d4cd4047c3a9c41eb9427082c59753dd5 -2026-07-27-session-projection-and-command-log.zh.md: b55704f28799008b5e2323d846696e4768fcd6d6 +2026-07-27-session-projection-and-command-log.md: 2fc463ab6e65998c19b0a0fce186c7eb17dacbf6 +2026-07-27-session-projection-and-command-log.zh.md: 89877d3aabcd074863f912b334c927268014e4d8 diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md index 8226325d4c..2fc463ab6e 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md @@ -123,7 +123,7 @@ Two log-only (non-surface, model-invisible) events, mirroring the `tool/call`/`t 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -The host command executor (`packages/ui/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep doing what they do today (plan's narration, goal's inject) — that is a domain decision, unchanged. +The host command executor (`packages/interaction/commands`) appends `command/run` before invoking the handler and `command/done` at settlement — direct standalone appends on the receiving agent's session, in the same shape as every other plugin-owned log-only event after the [synthetic-turn removal](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md): no turn wraps them (turns describe model-loop executions only), persistence drains them at ordinary checkpoints, and the commands package's own invariant companion enforces the run/done pairing. The payload is structured — `name` and, by default, `args` are the parser's own split (`parseCommand`'s name and rawInput), so a consumer (a projection unit folding its own command records, a rich command card) never re-parses a line. A definition sets `recordInput: false` when its authoritative domain event owns the payload; `command/run` then omits `args` rather than duplicating it. `text` is the handler's verbatim outcome — factual data of the same nature as `tool/result.content`, not presentation (how it is laid out remains client-computed at render time, satisfying the "presentation never enters the log" red line). Domains that want the model to know the outcome keep doing what they do today (plan's narration, goal's inject) — that is a domain decision, unchanged. Because committed events broadcast on the mux stream, refresh persistence, multi-tab sync, and fork/resume recovery all come for free. The `command.execute` RPC degrades to admission — `{ matched, commandId? }`: whether the line resolved, and the minted pairing id when it did, so the issuing client can correlate its request with the flow node the lifecycle events produce. The one-shot notice channel (`runDetached` → `noticeFor`) is retired. diff --git a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md index b55704f287..89877d3aab 100644 --- a/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.zh.md @@ -123,7 +123,7 @@ type UseProjection = { 'command/done': { commandId: string; kind: 'success' | 'error'; text?: string } ``` -host 侧命令执行器(`packages/ui/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent 的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域继续做它们今天在做的事(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 +host 侧命令执行器(`packages/interaction/commands`)在调用处理器前追加 `command/run`,在结算时追加 `command/done`——在接收 agent 的会话上直接独立追加,与[合成轮次移除](../../implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)之后所有插件自有 log-only 事件同一形状:没有轮次包裹它们(轮次只描述模型循环执行),持久化在常规检查点排空它们,run/done 配对由 commands 包自己的 invariant 伴生插件把守。载荷是结构化的——`name` 以及默认携带的 `args` 来自解析器自己的切分(`parseCommand` 的 name 与 rawInput),因此消费方(折叠自己命令记录的投影单元、富命令卡片)永远无需重新解析行文本。当载荷由权威领域事件持有时,命令定义会设置 `recordInput: false`;此时 `command/run` 省略 `args`,而不是重复该载荷。`text` 是处理器的原样结果——与 `tool/result.content` 同一性质的事实数据,不是呈现(版式如何编排仍由客户端在渲染时计算,满足「呈现永不入日志」这条红线)。想让模型知道结果的领域继续做它们今天在做的事(plan 的旁白、goal 的注入)——那是领域自己的决定,保持不变。 由于已提交事件会在 mux 流上广播,刷新后仍在、多标签页同步、fork/恢复后可还原这三件事随之全部自动获得。`command.execute` RPC 退化为准入判定——`{ matched, commandId? }`:该行是否匹配命中,以及命中时新铸的配对 id,发起命令的客户端据此把自己的请求与生命周期事件产出的 flow 节点关联起来。一次性通知通道(`runDetached` → `noticeFor`)就此下线。 diff --git a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml index cdebeeac5c..71d6fa82b5 100644 --- a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md -2026-07-19-make-jsonrpc-directional.md: 910b4988aca34dec499b2e34cb2a42042c81b0cb -2026-07-19-make-jsonrpc-directional.zh.md: 9810cf758faa078583955ba541e2b2fe9ad9c855 +2026-07-19-make-jsonrpc-directional.md: 945d58a0c989dac2beeb129bfd545fe5674c63cd +2026-07-19-make-jsonrpc-directional.zh.md: 181aab8cd2f2473f0b2694df1dff072fd805e69d diff --git a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md index 910b4988ac..945d58a0c9 100644 --- a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md +++ b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.md @@ -20,11 +20,11 @@ Return the settled outcome directly from `session/prompt` as `{ status, reason } ## Implementation plan -1. In `packages/ui/jsonrpc/src/server.ts`, replace `SessionPromptResult.accepted` with `status: 'ok' | 'error' | 'aborted'` and the captured `TurnEndReason`. `HarnessSdkServer.prompt()` will return `completed` as `ok`, `aborted` as `aborted`, and every other current or merge-extensible reason as `error`; reaching idle without a `turn/end` remains an invariant error. Remove only `session.finished`, leaving `session.event`, `subagent.started`, and `subagent.finished` unchanged. -2. In `packages/sdk/sdk-protocol/src/transport.ts`, narrow the shared class to the directions with consumers — inbound requests/outbound responses (the server) and outbound requests/inbound responses plus inbound notifications (the TypeScript SDK client) — removing only server-originated `request()` use and client-originated notification dispatch, or split the class into a server-side and client-side transport. Request result, method-not-found, and handler-error responses retain their current behavior and remain ordered after notifications emitted by the awaited handler. +1. In `packages/scaffold/server/src/server.ts`, replace `SessionPromptResult.accepted` with `status: 'ok' | 'error' | 'aborted'` and the captured `TurnEndReason`. `HarnessSdkServer.prompt()` will return `completed` as `ok`, `aborted` as `aborted`, and every other current or merge-extensible reason as `error`; reaching idle without a `turn/end` remains an invariant error. Remove only `session.finished`, leaving `session.event`, `subagent.started`, and `subagent.finished` unchanged. +2. In `packages/scaffold/protocol/src/transport.ts`, narrow the shared class to the directions with consumers — inbound requests/outbound responses (the server) and outbound requests/inbound responses plus inbound notifications (the TypeScript SDK client) — removing only server-originated `request()` use and client-originated notification dispatch, or split the class into a server-side and client-side transport. Request result, method-not-found, and handler-error responses retain their current behavior and remain ordered after notifications emitted by the awaited handler. 3. In `python/sdk/src/deepseek_harness/client.py`, `models.py`, and `__init__.py`, remove `IncomingRequest`, `_requests`, `notify()`, `next_request()`, `respond()`, and `respond_error()`. Add a public validated `SessionPromptResponse` carrying status and reason, return it from `session_prompt()`, and keep an explicit reader guard that ignores unexpected server-request frames instead of allowing them to match a response waiter. 4. In `python/sdk/src/deepseek_harness/api.py`, build `TurnResult.status` and a new `TurnResult.reason` from `SessionPromptResponse`, then delete the `session.finished` branch and second completion loop. Keep the subscription open during the request and preserve `_request_raw()`'s final notification drain so the last `turn/end` event and any subagent notification written before the response are collected before `Session.run()` reconstructs the final assistant message. -5. Replace the symmetric transport-pair cases in `packages/sdk/sdk-protocol/tests/transport.spec.ts` with per-direction coverage, and update `server.spec.ts`, `plugin-apply.spec.ts`, and `built-scope-carrier.e2e.ts` for direct outcomes, ordering, overlap, shutdown, and the narrowed fake; update the TypeScript SDK client (`packages/sdk/sdk-client`) and its suites for response-based settlement. Update `python/sdk/tests/test_client.py` for response-based settlement, unexpected-request-frame handling, callback and concurrency behavior, and the removed public helpers. Update the JSON-RPC and bilingual Python SDK READMEs, export JSDoc and declarations, `scripts/smoke-python-runtime.py`, and the Python single-executable snapshot. +5. Replace the symmetric transport-pair cases in `packages/scaffold/protocol/tests/transport.spec.ts` with per-direction coverage, and update `server.spec.ts`, `plugin-apply.spec.ts`, and `built-scope-carrier.e2e.ts` for direct outcomes, ordering, overlap, shutdown, and the narrowed fake; update the TypeScript SDK client (`packages/scaffold/client`) and its suites for response-based settlement. Update `python/sdk/tests/test_client.py` for response-based settlement, unexpected-request-frame handling, callback and concurrency behavior, and the removed public helpers. Update the JSON-RPC and bilingual Python SDK READMEs, export JSDoc and declarations, `scripts/smoke-python-runtime.py`, and the Python single-executable snapshot. ## Alternatives considered diff --git a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.zh.md b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.zh.md index 9810cf758f..181aab8cd2 100644 --- a/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.zh.md +++ b/.agents/notes/proposed/simplification/2026-07-19-make-jsonrpc-directional.zh.md @@ -20,11 +20,11 @@ JSON-RPC 桥接层把两个端点都建模为对称的对等端,但实际协 ## 实施计划 -1. 在 `packages/ui/jsonrpc/src/server.ts` 中,用 `status: 'ok' | 'error' | 'aborted'` 和捕获的 `TurnEndReason` 替换 `SessionPromptResult.accepted`。`HarnessSdkServer.prompt()` 把 `completed` 映射为 `ok`,把 `aborted` 映射为 `aborted`,把其他当前已有或可通过声明合并扩展的原因映射为 `error`;进入空闲状态却没有 `turn/end` 仍视为不变量错误。只删除 `session.finished`,保持 `session.event`、`subagent.started` 和 `subagent.finished` 不变。 -2. 在 `packages/sdk/sdk-protocol/src/transport.ts` 中,把共享类收窄到有消费者的方向——入站请求/出站响应(服务端)与出站请求/入站响应及入站通知(TypeScript SDK 客户端)——只删除服务端发起的 `request()` 用法与客户端发起的通知分发,或把该类拆分为服务端与客户端两个传输。请求结果、方法不存在与处理器错误响应保持原有行为,并继续排在被等待处理器发出的通知之后。 +1. 在 `packages/scaffold/server/src/server.ts` 中,用 `status: 'ok' | 'error' | 'aborted'` 和捕获的 `TurnEndReason` 替换 `SessionPromptResult.accepted`。`HarnessSdkServer.prompt()` 把 `completed` 映射为 `ok`,把 `aborted` 映射为 `aborted`,把其他当前已有或可通过声明合并扩展的原因映射为 `error`;进入空闲状态却没有 `turn/end` 仍视为不变量错误。只删除 `session.finished`,保持 `session.event`、`subagent.started` 和 `subagent.finished` 不变。 +2. 在 `packages/scaffold/protocol/src/transport.ts` 中,把共享类收窄到有消费者的方向——入站请求/出站响应(服务端)与出站请求/入站响应及入站通知(TypeScript SDK 客户端)——只删除服务端发起的 `request()` 用法与客户端发起的通知分发,或把该类拆分为服务端与客户端两个传输。请求结果、方法不存在与处理器错误响应保持原有行为,并继续排在被等待处理器发出的通知之后。 3. 在 `python/sdk/src/deepseek_harness/client.py`、`models.py` 和 `__init__.py` 中,删除 `IncomingRequest`、`_requests`、`notify()`、`next_request()`、`respond()` 和 `respond_error()`。新增公开且经过校验的 `SessionPromptResponse` 来携带状态与原因,由 `session_prompt()` 返回该对象,并保留明确的读取保护:忽略意外的服务端请求帧,避免它们命中响应等待器。 4. 在 `python/sdk/src/deepseek_harness/api.py` 中,根据 `SessionPromptResponse` 构造 `TurnResult.status` 和新增的 `TurnResult.reason`,再删除 `session.finished` 分支与第二个完成循环。请求期间保持订阅打开,并保留 `_request_raw()` 最后的通知排空步骤,确保写在响应前的最后一条 `turn/end` 事件与任何 subagent 通知,都会在 `Session.run()` 重建最终助手消息之前被收集。 -5. 用按方向的覆盖替换 `packages/sdk/sdk-protocol/tests/transport.spec.ts` 中的对称传输对用例,并更新 `server.spec.ts`、`plugin-apply.spec.ts` 和 `built-scope-carrier.e2e.ts`,覆盖直接结果、顺序、重叠、关闭和收窄后的伪实现;同步更新 TypeScript SDK 客户端(`packages/sdk/sdk-client`)及其套件以采用基于响应的结束流程。更新 `python/sdk/tests/test_client.py`,覆盖基于响应的结束流程、意外请求帧处理、回调与并发行为,以及已删除的公开辅助方法。同步更新 JSON-RPC README、双语 Python SDK README、导出 JSDoc 与声明、`scripts/smoke-python-runtime.py` 和 Python 单可执行文件快照。 +5. 用按方向的覆盖替换 `packages/scaffold/protocol/tests/transport.spec.ts` 中的对称传输对用例,并更新 `server.spec.ts`、`plugin-apply.spec.ts` 和 `built-scope-carrier.e2e.ts`,覆盖直接结果、顺序、重叠、关闭和收窄后的伪实现;同步更新 TypeScript SDK 客户端(`packages/scaffold/client`)及其套件以采用基于响应的结束流程。更新 `python/sdk/tests/test_client.py`,覆盖基于响应的结束流程、意外请求帧处理、回调与并发行为,以及已删除的公开辅助方法。同步更新 JSON-RPC README、双语 Python SDK README、导出 JSDoc 与声明、`scripts/smoke-python-runtime.py` 和 Python 单可执行文件快照。 ## 备选方案 diff --git a/AGENTS.md b/AGENTS.md index 7727539bd9..7c9b795ddc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,9 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// settings/ user-settings seam + file-backed provider credentials/ credential-reference seam + env-over-.env provider acp/ automation-only Agent Client Protocol server - ui/ JSON-RPC bridge; boot, approval, interaction plugins + interaction/ approval/interaction seams, permission, commands, ask-user tool + boot/ shared app-bin boot glue + scaffold/ project tooling: helper, launcher, initializer, SDK wire protocol + ends examples/ demo bundles (agent-spine + CLI/ACP/JSON-RPC bins) leaves load support/ dev/test infrastructure util/ zero-dependency utilities diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 36c4bad6dd..d830e8fba6 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -18,7 +18,7 @@ "path": "../../vendor/include" }, { - "path": "../../packages/ui/app-boot" + "path": "../../packages/boot/app-boot" }, { "path": "../../packages/bundle/base" diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 4d10310333..76491a37fe 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: c14d68c47f6ed0786e8fc0b0650dbc67678250fb -architecture.zh.md: df484762d29e170a5b1ba64f0637c5170c2c5ef4 +architecture.md: cee159452fc536c98a006cfa3ea92e9d21a1e77b +architecture.zh.md: cf10e60a5d5e4fe3f39d72bef3d48fe7c2f3c105 diff --git a/docs/architecture.md b/docs/architecture.md index c14d68c47f..cee159452f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -163,7 +163,7 @@ Exceptions combine LLM interface/consumer, filesystem policy, web registries, an ### Bundles And Apps -`dsh-agent-spine-demo` bundles a spine and optional goals. App packages own CLI, ACP automation, and JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [acp/](../packages/acp/README.md), [ui/](../packages/ui/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK defaults when config is absent ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). +`dsh-agent-spine-demo` bundles a spine and optional goals. App packages own CLI, ACP automation, and JSON-RPC front doors ([README](../packages/examples/agent-spine-demo/README.md), [acp/](../packages/acp/README.md), [interaction/](../packages/interaction/README.md)). `dsh-jsonrpc-agent` boots external `cordis.yml`; the Python SDK defaults when config is absent ([Python SDK](../python/README.md)). Thin deployments use swappable backends and optional tools ([examples/](../examples/AGENTS.md), [runnable wirings](cookbook/extension-cookbook.md#runnable-wirings), [graph atlas](graph-atlas.md)). ### Where New Behavior Goes diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index df484762d2..cf10e60a5d 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -163,7 +163,7 @@ idle inject: ### 组合包与应用 -`dsh-agent-spine-demo` 组合一套主干和可选目标。应用包负责 CLI(命令行界面)、ACP 自动化入口和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[ui/](../packages/ui/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 在配置缺失时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。 +`dsh-agent-spine-demo` 组合一套主干和可选目标。应用包负责 CLI(命令行界面)、ACP 自动化入口和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[interaction/](../packages/interaction/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 在配置缺失时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。 ### 新行为的归属位置 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 60a19999f3..c191f6a659 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -389,10 +389,10 @@ flowchart LR | `ctx.sessionReferences` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax. | | `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm), [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | | `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | -| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/ui/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | -| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/ui/user-interaction) | - | [`tool-ask-user`](../packages/ui/tool-ask-user) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. | +| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | +| `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/interaction/user-interaction) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. | | `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions. | -| `ctx.commands` | `core` | [`commands`](../packages/ui/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | +| `ctx.commands` | `core` | [`commands`](../packages/interaction/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | | `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title), [`host-apiproxy`](../packages/host/apiproxy) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and api-proxy serves baselines and pushes changed values. | | `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`host-apiproxy`](../packages/host/apiproxy) | - | Durably checkpoints projection unit states per session (throttled + turn/end/detach mandatory points) and serves the cold-read ladder: cache row + persistence tail replay, so listings never load full logs. | | `ctx.skills` | `seam` | [`skill`](../packages/skill/skill) | [`skill-badge`](../packages/skill/skill-badge), [`skill-local`](../packages/skill/skill-local) | [`tool-skill`](../packages/skill/tool-skill) | - | Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies. | @@ -407,7 +407,7 @@ flowchart LR | `ctx.sandbox` | `seam` | [`sandbox`](../packages/sandbox/sandbox) | [`sandbox-local`](../packages/sandbox/sandbox-local) | [`bash-sandbox`](../packages/bash/bash-sandbox), [`pty-local`](../packages/pty/pty-local) | - | Consumers hand over the exact argv they are about to spawn; same-world backends wrap it under a per-call policy and report enforcement. | | `ctx.sandboxPolicy` | `core` | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | - | [`bash-sandbox`](../packages/bash/bash-sandbox), [`fs-sandbox`](../packages/fs/fs-sandbox), [`pty-local`](../packages/pty/pty-local) | - | The one home for the deployment default mode + workspace root; only the sandboxed executor and provider read the service (the tool layers use the pure `sandbox/mode` fold it also exports). Both enforcing families read it so bash and fs cannot confine to different roots. | | `ctx.approval` | `seam` | `approval` | [`acp`](../packages/acp/acp) | [`tools`](../packages/core/tools), [`tool-bash`](../packages/bash/tool-bash) | - | One-shot permission decisions dispatched over the `approval/request` waterfall; answerers are listeners (the ACP bridge for its own agents), absence fails closed to `unavailable`. | -| `ctx.permission` | `core` | [`permission`](../packages/ui/permission) | - | - | - | User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events. | +| `ctx.permission` | `core` | [`permission`](../packages/interaction/permission) | - | - | - | User-facing preset table (`workspace-write`/`danger-full-access`) bundling the sandbox-mode and approval-policy knobs; a switch writes one `permission/preset` event through to both knob events. | | `ctx.codeRuntime` | `seam` | [`code-runtime`](../packages/code-runtime/code-runtime) | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | [`tools`](../packages/core/tools) | - | Runs one model-written program against host-provided async bindings; backends differ by substrate and language (the tool registry consumes it for Code Mode). | | `ctx.fs` | `seam` | [`fs`](../packages/fs/fs) | [`fs-local`](../packages/fs/fs-local), [`fs-sandbox`](../packages/fs/fs-sandbox), [`fs-e2b`](../packages/e2b/fs-e2b) | [`tool-fs`](../packages/fs/tool-fs) | [`fs-policy`](../packages/fs/fs-policy) | tool-fs executes read/write/edit through ctx.fs; fs-sandbox fences mutations by the shared sandbox mode; fs-policy contributes observed-state checks through the fs/* event gate. | | `ctx.compact` | `seam` | [`compact`](../packages/compact/compact) | [`compact-basic`](../packages/compact/compact-basic) | [`compact-basic`](../packages/compact/compact-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; a model-facing compact tool remains deferred. | diff --git a/docs/core-data-structures/approval.i18n.yaml b/docs/core-data-structures/approval.i18n.yaml index 0679cd47bd..975f631805 100644 --- a/docs/core-data-structures/approval.i18n.yaml +++ b/docs/core-data-structures/approval.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/approval.md -approval.md: f1889b25e2bbbcb157b0bced070b1f157a867504 -approval.zh.md: 48222991312f9ae97c9249f1232b261d2393d282 +approval.md: 8f17f98101950e6413d67fb4cce6befc5aede069 +approval.zh.md: c19285572b667512427b71d4af214acbc2baf334 diff --git a/docs/core-data-structures/approval.md b/docs/core-data-structures/approval.md index f1889b25e2..8f17f98101 100644 --- a/docs/core-data-structures/approval.md +++ b/docs/core-data-structures/approval.md @@ -2,9 +2,9 @@ English | [中文](approval.zh.md) -The user-approval seam of [dsh-user-approval](../../packages/ui/user-approval) answers one question: may this specific action proceed? It owns the shared request/outcome vocabulary, the `ctx.approval` dispatch service, the `approval/request` answerer waterfall, the log-only audit pair, and the per-session `ask`/`never` policy. UI channels may provide human answerers; the [ACP automation bridge](../../packages/acp/acp) provides one-shot machine decisions for its own agents. Callers such as [dsh-tools](../../packages/core/tools) and [dsh-tool-bash](../../packages/bash/tool-bash) consume the closed outcome and fail closed unless it is `allowed-once`. +The user-approval seam of [dsh-user-approval](../../packages/interaction/user-approval) answers one question: may this specific action proceed? It owns the shared request/outcome vocabulary, the `ctx.approval` dispatch service, the `approval/request` answerer waterfall, the log-only audit pair, and the per-session `ask`/`never` policy. UI channels may provide human answerers; the [ACP automation bridge](../../packages/acp/acp) provides one-shot machine decisions for its own agents. Callers such as [dsh-tools](../../packages/core/tools) and [dsh-tool-bash](../../packages/bash/tool-bash) consume the closed outcome and fail closed unless it is `allowed-once`. -Source: [`packages/ui/user-approval/src/index.ts`](../../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts`](../../packages/interaction/user-approval/src/index.ts) ## Identity and outcome diff --git a/docs/core-data-structures/approval.zh.md b/docs/core-data-structures/approval.zh.md index 4822299131..c19285572b 100644 --- a/docs/core-data-structures/approval.zh.md +++ b/docs/core-data-structures/approval.zh.md @@ -2,9 +2,9 @@ [English](approval.md) | 中文 -[dsh-user-approval](../../packages/ui/user-approval) 的用户审批 seam 回答一个问题:这个具体操作是否可以继续?它拥有共享的请求/结果词汇、`ctx.approval` 分发服务、`approval/request` 应答者 waterfall(瀑布式事件)、仅记录日志的审计事件对,以及按会话的 `ask`/`never` 策略。UI 通道可以提供人类应答者;[ACP(Agent Client Protocol)自动化桥接层](../../packages/acp/acp)为其拥有的 agent(智能体)提供一次性机器决策。调用方如 [dsh-tools](../../packages/core/tools) 和 [dsh-tool-bash](../../packages/bash/tool-bash) 消费闭合的结果,除非结果为 `allowed-once`,否则一律拒绝。 +[dsh-user-approval](../../packages/interaction/user-approval) 的用户审批 seam 回答一个问题:这个具体操作是否可以继续?它拥有共享的请求/结果词汇、`ctx.approval` 分发服务、`approval/request` 应答者 waterfall(瀑布式事件)、仅记录日志的审计事件对,以及按会话的 `ask`/`never` 策略。UI 通道可以提供人类应答者;[ACP(Agent Client Protocol)自动化桥接层](../../packages/acp/acp)为其拥有的 agent(智能体)提供一次性机器决策。调用方如 [dsh-tools](../../packages/core/tools) 和 [dsh-tool-bash](../../packages/bash/tool-bash) 消费闭合的结果,除非结果为 `allowed-once`,否则一律拒绝。 -源码:[`packages/ui/user-approval/src/index.ts`](../../packages/ui/user-approval/src/index.ts) +源码:[`packages/interaction/user-approval/src/index.ts`](../../packages/interaction/user-approval/src/index.ts) ## 标识与结果 diff --git a/docs/core-data-structures/commands.i18n.yaml b/docs/core-data-structures/commands.i18n.yaml index 532151ef70..18df873acc 100644 --- a/docs/core-data-structures/commands.i18n.yaml +++ b/docs/core-data-structures/commands.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/commands.md -commands.md: 1390f736ff582a997ee710cfd27fce4e7db3461f -commands.zh.md: 53aec9bd138773df7fceb45e5d6c30b7205dd0d9 +commands.md: 83ce9c2498df58110d9a9c2e7d5163a9df813e1c +commands.zh.md: c486f480177c7ff8b35cc846beafac009cea42bb diff --git a/docs/core-data-structures/commands.md b/docs/core-data-structures/commands.md index 1390f736ff..83ce9c2498 100644 --- a/docs/core-data-structures/commands.md +++ b/docs/core-data-structures/commands.md @@ -2,9 +2,9 @@ English | [中文](commands.zh.md) -The human-command seam of [`dsh-commands`](../../packages/ui/commands). Interactive adapters use it to discover and directly execute plugin-owned commands for an exact agent without creating a model message. The [command Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) owns dispatch and lifecycle rationale; the [package README](../../packages/ui/commands/README.md) owns composition and limitations. +The human-command seam of [`dsh-commands`](../../packages/interaction/commands). Interactive adapters use it to discover and directly execute plugin-owned commands for an exact agent without creating a model message. The [command Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) owns dispatch and lifecycle rationale; the [package README](../../packages/interaction/commands/README.md) owns composition and limitations. -Source: [`packages/ui/commands/src/index.ts`](../../packages/ui/commands/src/index.ts) +Source: [`packages/interaction/commands/src/index.ts`](../../packages/interaction/commands/src/index.ts) ## Input metadata diff --git a/docs/core-data-structures/commands.zh.md b/docs/core-data-structures/commands.zh.md index 53aec9bd13..c486f48017 100644 --- a/docs/core-data-structures/commands.zh.md +++ b/docs/core-data-structures/commands.zh.md @@ -2,9 +2,9 @@ [English](commands.md) | 中文 -[`dsh-commands`](../../packages/ui/commands) 的用户命令 seam。交互式适配器用它发现插件拥有的命令,并针对确切的 agent(智能体)直接执行这些命令,而不创建模型消息。[命令 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) 负责分发与生命周期的决策依据;[包 README](../../packages/ui/commands/README.md) 负责组合方式与限制。 +[`dsh-commands`](../../packages/interaction/commands) 的用户命令 seam。交互式适配器用它发现插件拥有的命令,并针对确切的 agent(智能体)直接执行这些命令,而不创建模型消息。[命令 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.md) 负责分发与生命周期的决策依据;[包 README](../../packages/interaction/commands/README.md) 负责组合方式与限制。 -来源:[`packages/ui/commands/src/index.ts`](../../packages/ui/commands/src/index.ts) +来源:[`packages/interaction/commands/src/index.ts`](../../packages/interaction/commands/src/index.ts) ## 输入元数据 diff --git a/docs/core-data-structures/user-interaction.i18n.yaml b/docs/core-data-structures/user-interaction.i18n.yaml index 5480052231..3768cfcf7b 100644 --- a/docs/core-data-structures/user-interaction.i18n.yaml +++ b/docs/core-data-structures/user-interaction.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/user-interaction.md -user-interaction.md: 73727cbb754f10633d3213f67f7c3c9fdb215500 -user-interaction.zh.md: b6e95af4026e58c63c713d87ee9a86b3712b311f +user-interaction.md: ec22eb28e9554d6454bf2670f73f2e9b199df014 +user-interaction.zh.md: 814f8911e41aecf568f627f76f8239bac4ea719b diff --git a/docs/core-data-structures/user-interaction.md b/docs/core-data-structures/user-interaction.md index 73727cbb75..ec22eb28e9 100644 --- a/docs/core-data-structures/user-interaction.md +++ b/docs/core-data-structures/user-interaction.md @@ -2,9 +2,9 @@ English | [中文](user-interaction.zh.md) -The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`; the host runtime relays requests to its connected client. +The user-interaction seam of [dsh-user-interaction](../../packages/interaction/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`; the host runtime relays requests to its connected client. -Source: [`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts) +Source: [`packages/interaction/user-interaction/src/index.ts`](../../packages/interaction/user-interaction/src/index.ts) ## Question options diff --git a/docs/core-data-structures/user-interaction.zh.md b/docs/core-data-structures/user-interaction.zh.md index b6e95af402..814f8911e4 100644 --- a/docs/core-data-structures/user-interaction.zh.md +++ b/docs/core-data-structures/user-interaction.zh.md @@ -2,9 +2,9 @@ [English](user-interaction.md) | 中文 -[dsh-user-interaction](../../packages/ui/user-interaction) 的用户交互 seam。它是工具或权限插件需要人类回答后 agent(智能体)才能继续时所使用的、提供方无关的词汇。UI surface 提供活跃的 `UserInteractionProvider`;host 运行时把请求转发给它连接的客户端。 +[dsh-user-interaction](../../packages/interaction/user-interaction) 的用户交互 seam。它是工具或权限插件需要人类回答后 agent(智能体)才能继续时所使用的、提供方无关的词汇。UI surface 提供活跃的 `UserInteractionProvider`;host 运行时把请求转发给它连接的客户端。 -源码:[`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts) +源码:[`packages/interaction/user-interaction/src/index.ts`](../../packages/interaction/user-interaction/src/index.ts) ## 问题选项 diff --git a/docs/testing.i18n.yaml b/docs/testing.i18n.yaml index 90970c8c57..0b7cdfdc76 100644 --- a/docs/testing.i18n.yaml +++ b/docs/testing.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/testing.md -testing.md: fecf02fe887aac5c92c739523f10a0562eba6242 -testing.zh.md: a34f56fac72baa9916d5f96e83048ad54cf23d4a +testing.md: 296dd769118cc5498ddb1bb9c41e2c060b2a5bce +testing.zh.md: e9182495fbdbb1458c9ff5b30a9591f8b70f27c5 diff --git a/docs/testing.md b/docs/testing.md index fecf02fe88..296dd76911 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -32,7 +32,7 @@ An e2e assertion re-runs the command or re-reads the file externally; a keyword - Product-visible plugins require a non-unit REAL-composition test. Hand-built `ctx.plugin(...)` suites are insufficient: boot test-only `cordis.yml` through Loader and app/process, mock only external/nondeterministic boundaries, and assert model-visible request/log, durable state, or user-visible output. Keep opt-ins out of shipped defaults. - A guard only guards if the regression actually fails it. For a plugin without `inject` (bundle/composition plugins), a Loader smoke stays green under a broken export shape — add an explicit `expect('default' in mod).toBe(false)` plus an `unwrapExports` round-trip assertion, and prove it: introduce the regression, watch red, revert. -- "Real entry path" means the published artifact: a package `bin` runs built `lib/bin.js` under plain `node`, exposing failures tsx masks (settle races, module resolution, swallowed load failures). The same applies to non-index runtime entries (the worker-thread sibling `lib/worker.cjs`) and singleton modules shared across bundles (`packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts`). Keep the built-artifact smokes green (`packages/ui/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero. +- "Real entry path" means the published artifact: a package `bin` runs built `lib/bin.js` under plain `node`, exposing failures tsx masks (settle races, module resolution, swallowed load failures). The same applies to non-index runtime entries (the worker-thread sibling `lib/worker.cjs`) and singleton modules shared across bundles (`packages/scaffold/server/tests/built-scope-carrier.e2e.ts`). Keep the built-artifact smokes green (`packages/examples/*/tests/built-bin.e2e.ts`, `packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`), and assert a genuinely-missing config exits non-zero. ## Test resolution: source plane only diff --git a/docs/testing.zh.md b/docs/testing.zh.md index a34f56fac7..e9182495fb 100644 --- a/docs/testing.zh.md +++ b/docs/testing.zh.md @@ -32,7 +32,7 @@ e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身 - 产品可见的插件必须有一个非单元的真实组合测试。手动构建的 `ctx.plugin(...)` 套件不够:通过 Loader 和 app/process 启动仅用于测试的 `cordis.yml`,只 mock 外部/不确定边界,断言模型可见的请求/日志、持久状态或用户可见输出。不要把 opt-in 选项混入交付默认值。 - 一个守卫只有在回归真的能让它失败时才有效。对于没有 `inject` 的插件(bundle/组合插件),Loader 冒烟测试在导出形状损坏时仍然绿着——需要添加显式的 `expect('default' in mod).toBe(false)` 加 `unwrapExports` 往返断言,并证明它有效:引入回归、观察变红、回退。 -- 「真实入口路径」指已发布的产物:包的 `bin` 所运行的是构建后的 `lib/bin.js`,并由普通 `node` 执行,从而暴露 tsx 会掩盖的失败(等待稳定时的竞态、模块解析、被吞掉的加载失败)。同样的规则适用于非 index 运行时入口(worker-thread 的同级文件 `lib/worker.cjs`),也适用于多个 bundle 共享的单例模块(`packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts`)。保持构建产物冒烟测试绿色(`packages/ui/*/tests/built-bin.e2e.ts`、`packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`),并断言真正缺失的配置以非零状态退出。 +- 「真实入口路径」指已发布的产物:包的 `bin` 所运行的是构建后的 `lib/bin.js`,并由普通 `node` 执行,从而暴露 tsx 会掩盖的失败(等待稳定时的竞态、模块解析、被吞掉的加载失败)。同样的规则适用于非 index 运行时入口(worker-thread 的同级文件 `lib/worker.cjs`),也适用于多个 bundle 共享的单例模块(`packages/scaffold/server/tests/built-scope-carrier.e2e.ts`)。保持构建产物冒烟测试绿色(`packages/examples/*/tests/built-bin.e2e.ts`、`packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`),并断言真正缺失的配置以非零状态退出。 ## 测试解析:仅限源码 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index adb73f104d..0cb985d07d 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -107,7 +107,7 @@ Ask the user a concise question when you need confirmation, a choice, or missing } ``` -Source: [`packages/ui/tool-ask-user/src/index.ts`](../packages/ui/tool-ask-user/src/index.ts) +Source: [`packages/interaction/tool-ask-user/src/index.ts`](../packages/interaction/tool-ask-user/src/index.ts) ask_user_question pauses the tool call until the active UI provider returns a human answer. diff --git a/knip.json b/knip.json index 0e82064200..63db33e51c 100644 --- a/knip.json +++ b/knip.json @@ -513,7 +513,7 @@ "tests/**/*.ts" ] }, - "packages/ui/jsonrpc": { + "packages/scaffold/server": { "entry": [ "tests/**/*.spec.ts", "tests/**/*.e2e.ts" @@ -523,7 +523,7 @@ "tests/**/*.ts" ] }, - "packages/ui/commands": { + "packages/interaction/commands": { "entry": [ "tests/**/*.spec.ts" ], @@ -537,7 +537,7 @@ "src/**/*.ts" ] }, - "packages/sdk/create-sdk": { + "packages/scaffold/create-sdk": { "entry": [ "src/bin.ts", "tests/**/*.spec.ts", @@ -549,7 +549,7 @@ "tests/**/*.ts" ] }, - "packages/sdk/scripts": { + "packages/scaffold/scripts": { "entry": [ "src/bin.ts", "tests/**/*.spec.ts", diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 44dd906b76..68da8ae611 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: 9f8b3f936af6392239218d5389c91c87d0f7d1f9 -README.zh.md: 2f4e86cb541a8a34762b36cac57f456de96cac5d +README.md: 198caca8c8b8231ece4d7160c809ba7b881e775c +README.zh.md: bf2bf437796d904d135b610ff2c7a2d4d21b0298 diff --git a/packages/README.md b/packages/README.md index 9f8b3f936a..198caca8c8 100644 --- a/packages/README.md +++ b/packages/README.md @@ -45,9 +45,10 @@ Groups hold `packages///`; names stay `@deepseek-ai/dsh-`. **Gr | [`credentials/`](credentials/README.md) | Credential-reference seam + env-over-`.env` provider | Product — stable surface | | [`storage/`](storage/README.md) | Non-session storage hub + backends + domain form | Product — stable surface | | [`workspace/`](workspace/README.md) | Workspace entity | Product — stable surface | -| [`sdk/`](sdk/README.md) | Project SDK tooling | Product — stable surface | +| [`scaffold/`](scaffold/README.md) | Create/launch/drive project tooling: helper, launcher, initializer, wire protocol with both ends, launcher telemetry | Product — stable surface | | [`acp/`](acp/README.md) | Automation-only Agent Client Protocol server | Product — stable surface | -| [`ui/`](ui/README.md) | JSON-RPC integration, approval/interaction seams, ask-user tool | Product — stable surface | +| [`interaction/`](interaction/README.md) | Human-collaboration plane: approval/interaction seams, permission preset, commands, ask-user tool | Product — stable surface | +| [`boot/`](boot/README.md) | Shared app-bin boot glue | Product — stable surface | | [`host/`](host/README.md) | Web-GUI host half: API gateway + HTTP route server | Product — stable surface | | [`client/`](client/README.md) | Web-GUI browser half: shell, wire, object services, slots, `ui-*` plugins | Product — stable surface | | [`experimental/`](experimental/README.md) | Prototypes and internal plugins | Unreleased | diff --git a/packages/README.zh.md b/packages/README.zh.md index 2f4e86cb54..bf2bf43779 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -45,9 +45,10 @@ | [`credentials/`](credentials/README.md) | 凭据引用 seam + 环境叠加 `.env` provider | 产品:稳定表面 | | [`storage/`](storage/README.md) | 非会话存储中枢 + 后端 + 领域形式 | 产品:稳定表面 | | [`workspace/`](workspace/README.md) | Workspace 实体 | 产品:稳定表面 | -| [`sdk/`](sdk/README.md) | 项目 SDK 工具 | 产品:稳定表面 | +| [`scaffold/`](scaffold/README.md) | 创建/启动/驱动项目的工具:helper、启动器、初始化器、带两端的通信协议、启动器 telemetry | 产品:稳定表面 | | [`acp/`](acp/README.md) | 仅面向自动化的 Agent Client Protocol 服务器 | 产品:稳定表面 | -| [`ui/`](ui/README.md) | JSON-RPC 集成、批准/交互 seam、用户问答工具 | 产品:稳定表面 | +| [`interaction/`](interaction/README.md) | 人机协作平面:批准/交互 seam、权限预设、命令、用户问答工具 | 产品:稳定表面 | +| [`boot/`](boot/README.md) | 共享的 app bin 启动粘合层 | 产品:稳定表面 | | [`host/`](host/README.md) | web GUI 宿主半侧:API 网关 + HTTP 路由服务器 | 产品:稳定表面 | | [`client/`](client/README.md) | web GUI 浏览器半侧:shell、协议层、对象服务、slot、`ui-*` 插件 | 产品:稳定表面 | | [`experimental/`](experimental/README.md) | 原型和内部插件 | 未发布 | diff --git a/packages/acp/acp/tsconfig.json b/packages/acp/acp/tsconfig.json index 3109e0eea8..177b3cb3cd 100644 --- a/packages/acp/acp/tsconfig.json +++ b/packages/acp/acp/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../core/agent" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" }, { "path": "../../support/invariants" diff --git a/packages/bash/tool-bash/tsconfig.json b/packages/bash/tool-bash/tsconfig.json index b122ed58ca..93cbe93786 100644 --- a/packages/bash/tool-bash/tsconfig.json +++ b/packages/bash/tool-bash/tsconfig.json @@ -39,7 +39,7 @@ "path": "../../bash/bash-env" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" }, { "path": "../../sandbox/sandbox" diff --git a/packages/sdk/README.i18n.yaml b/packages/boot/README.i18n.yaml similarity index 57% rename from packages/sdk/README.i18n.yaml rename to packages/boot/README.i18n.yaml index 738a0610ab..151e5e5c08 100644 --- a/packages/sdk/README.i18n.yaml +++ b/packages/boot/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/README.md -README.md: 3f99d6d45ddc64ac068457b0d03e533d21451a5f -README.zh.md: 003383c5f6238b37893927813959d52d96350c3e +# pnpm run verify-translation-pairing --write packages/boot/README.md +README.md: 7727f4fbab62a930051fd3b92d04513ac9f5d218 +README.zh.md: aa9a56619ee55c3be8feb58881f77c460861b1ac diff --git a/packages/boot/README.md b/packages/boot/README.md new file mode 100644 index 0000000000..7727f4fbab --- /dev/null +++ b/packages/boot/README.md @@ -0,0 +1,9 @@ +# boot/ — shared app-bin boot glue + +English | [中文](README.zh.md) + +The channel-neutral boot library the app bins share. A role-complete single-package group: it belongs to no channel and no assembly — `apps/cli`, the [`scaffold/`](../scaffold/README.md) launcher, and the [`examples/`](../examples/README.md) demo bins all consume it. + +| Package | Role | ctx key | +|---|---|---| +| `app-boot/` | Shared boot glue for the app bins: `.env` loading, fail-loud Loader guards, snapshot-aware config resolution, the settle-the-tree boot sequence | (library for the bins) | diff --git a/packages/boot/README.zh.md b/packages/boot/README.zh.md new file mode 100644 index 0000000000..aa9a56619e --- /dev/null +++ b/packages/boot/README.zh.md @@ -0,0 +1,9 @@ +# boot/:共享的 app bin 启动粘合层 + +[English](README.md) | 中文 + +各 app bin 共享、与通道无关的启动库。一个角色完备的单包组:它不归属任何通道也不归属任何组装——`apps/cli`、[`scaffold/`](../scaffold/README.md) 启动器和 [`examples/`](../examples/README.md) 各演示 bin 都消费它。 + +| 包 | 职责 | ctx 键 | +|---|---|---| +| `app-boot/` | app bin 的共享启动粘合层:加载 `.env`、会明确报错的 Loader 保护机制、感知快照的配置解析,以及等待整棵树停稳的启动序列 | (供各 bin 使用的库) | diff --git a/packages/ui/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml similarity index 81% rename from packages/ui/app-boot/README.i18n.yaml rename to packages/boot/app-boot/README.i18n.yaml index 24f04a21c1..3ae6f5427c 100644 --- a/packages/ui/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/app-boot/README.md +# pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md README.md: c256b89288e3e384c1dd3e64629a06d7cfef31f6 README.zh.md: 88d1c4ad0ced2f5a6440a1b64e34e738a842f938 diff --git a/packages/ui/app-boot/README.md b/packages/boot/app-boot/README.md similarity index 100% rename from packages/ui/app-boot/README.md rename to packages/boot/app-boot/README.md diff --git a/packages/ui/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md similarity index 100% rename from packages/ui/app-boot/README.zh.md rename to packages/boot/app-boot/README.zh.md diff --git a/packages/ui/app-boot/package.json b/packages/boot/app-boot/package.json similarity index 100% rename from packages/ui/app-boot/package.json rename to packages/boot/app-boot/package.json diff --git a/packages/ui/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts similarity index 100% rename from packages/ui/app-boot/src/index.ts rename to packages/boot/app-boot/src/index.ts diff --git a/packages/ui/app-boot/src/invariant.ts b/packages/boot/app-boot/src/invariant.ts similarity index 100% rename from packages/ui/app-boot/src/invariant.ts rename to packages/boot/app-boot/src/invariant.ts diff --git a/packages/ui/app-boot/src/profile.ts b/packages/boot/app-boot/src/profile.ts similarity index 100% rename from packages/ui/app-boot/src/profile.ts rename to packages/boot/app-boot/src/profile.ts diff --git a/packages/ui/app-boot/tests/app-boot.spec.ts b/packages/boot/app-boot/tests/app-boot.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/app-boot.spec.ts rename to packages/boot/app-boot/tests/app-boot.spec.ts diff --git a/packages/ui/app-boot/tests/config-dump.spec.ts b/packages/boot/app-boot/tests/config-dump.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/config-dump.spec.ts rename to packages/boot/app-boot/tests/config-dump.spec.ts diff --git a/packages/ui/app-boot/tests/config-reload.spec.ts b/packages/boot/app-boot/tests/config-reload.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/config-reload.spec.ts rename to packages/boot/app-boot/tests/config-reload.spec.ts diff --git a/packages/ui/app-boot/tests/hmr-config.spec.ts b/packages/boot/app-boot/tests/hmr-config.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/hmr-config.spec.ts rename to packages/boot/app-boot/tests/hmr-config.spec.ts diff --git a/packages/ui/app-boot/tests/profile.spec.ts b/packages/boot/app-boot/tests/profile.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/profile.spec.ts rename to packages/boot/app-boot/tests/profile.spec.ts diff --git a/packages/ui/app-boot/tests/repository-cache.spec.ts b/packages/boot/app-boot/tests/repository-cache.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/repository-cache.spec.ts rename to packages/boot/app-boot/tests/repository-cache.spec.ts diff --git a/packages/ui/app-boot/tests/user-patches.spec.ts b/packages/boot/app-boot/tests/user-patches.spec.ts similarity index 100% rename from packages/ui/app-boot/tests/user-patches.spec.ts rename to packages/boot/app-boot/tests/user-patches.spec.ts diff --git a/packages/ui/app-boot/tsconfig.json b/packages/boot/app-boot/tsconfig.json similarity index 100% rename from packages/ui/app-boot/tsconfig.json rename to packages/boot/app-boot/tsconfig.json diff --git a/packages/ui/app-boot/tsdown.config.ts b/packages/boot/app-boot/tsdown.config.ts similarity index 100% rename from packages/ui/app-boot/tsdown.config.ts rename to packages/boot/app-boot/tsdown.config.ts diff --git a/packages/bundle/README.i18n.yaml b/packages/bundle/README.i18n.yaml index 27e50409cd..5199e0939e 100644 --- a/packages/bundle/README.i18n.yaml +++ b/packages/bundle/README.i18n.yaml @@ -1,6 +1,12 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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: +<<<<<<<< HEAD:packages/bundle/README.i18n.yaml # pnpm run verify-translation-pairing --write packages/bundle/README.md README.md: 4759170435a80e85731446cef21d24fff2abed66 README.zh.md: 1ef610a1b7b3c591c9a900e04f2d8096b0b086b9 +======== +# pnpm run verify-translation-pairing --write packages/interaction/tui/README.md +README.md: c81cac891403e5294c4456ce4d4048ecd74666ce +README.zh.md: 01055619f4df460284564f0a1816de366d809e01 +>>>>>>>> a58746f4cc (refactor(packages): dissolve ui/ and rename sdk/ to scaffold/):packages/interaction/tui/README.i18n.yaml diff --git a/packages/client/connection/tsconfig.json b/packages/client/connection/tsconfig.json index 6ff7fbfb25..58c04d18bd 100644 --- a/packages/client/connection/tsconfig.json +++ b/packages/client/connection/tsconfig.json @@ -16,7 +16,7 @@ "path": "../../core/session" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../../util/brand" @@ -28,10 +28,10 @@ "path": "../../host/webserver" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" }, { - "path": "../../ui/user-interaction" + "path": "../../interaction/user-interaction" }, { "path": "../../support/invariants" diff --git a/packages/client/runtime/tsconfig.json b/packages/client/runtime/tsconfig.json index 71cfb229a9..867b2f588c 100644 --- a/packages/client/runtime/tsconfig.json +++ b/packages/client/runtime/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../host/apiproxy" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../../compact/compact" diff --git a/packages/client/ui-conversation/tsconfig.json b/packages/client/ui-conversation/tsconfig.json index 0505786841..89d0067caf 100644 --- a/packages/client/ui-conversation/tsconfig.json +++ b/packages/client/ui-conversation/tsconfig.json @@ -51,7 +51,7 @@ "path": "../../support/invariants" }, { - "path": "../../ui/permission" + "path": "../../interaction/permission" } ], "exclude": [ diff --git a/packages/client/ui-permission/tsconfig.json b/packages/client/ui-permission/tsconfig.json index 32f84d6a2d..bfc71e2e3c 100644 --- a/packages/client/ui-permission/tsconfig.json +++ b/packages/client/ui-permission/tsconfig.json @@ -39,7 +39,7 @@ "path": "../web-react" }, { - "path": "../../ui/permission" + "path": "../../interaction/permission" }, { "path": "../../support/invariants" diff --git a/packages/client/ui-question/tsconfig.json b/packages/client/ui-question/tsconfig.json index 6b5b0acc3a..1b920ce207 100644 --- a/packages/client/ui-question/tsconfig.json +++ b/packages/client/ui-question/tsconfig.json @@ -30,7 +30,7 @@ "path": "../ui-slots" }, { - "path": "../../ui/tool-ask-user" + "path": "../../interaction/tool-ask-user" }, { "path": "../../support/invariants" diff --git a/packages/compact/command-compact/tsconfig.json b/packages/compact/command-compact/tsconfig.json index f99f2b98b4..c1040bf92a 100644 --- a/packages/compact/command-compact/tsconfig.json +++ b/packages/compact/command-compact/tsconfig.json @@ -15,7 +15,7 @@ "path": "../../../vendor/cordis" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../compact" diff --git a/packages/compact/compact/README.i18n.yaml b/packages/compact/compact/README.i18n.yaml index 6463edbc33..1ab398842f 100644 --- a/packages/compact/compact/README.i18n.yaml +++ b/packages/compact/compact/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/compact/compact/README.md -README.md: cd2404cda8d8702ca0400d3f23d9e1fe041495a1 -README.zh.md: ed6ccc630118dc1bd6d6ea761beefafadb4bd48f +README.md: 5703540435b976cfc03987edca28e2b5cd24ae0d +README.zh.md: 434b9bfa3d0d7f21fc262929ac72ff5ab15d5057 diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index cd2404cda8..5703540435 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -68,7 +68,7 @@ Subclass `CompactService`, implement `compactIfNeeded`, `compactNow`, and `compa ## Recognizing a checkpoint outside the host program (`./checkpoint`) -`COMPACT_CHECKPOINT_SOURCE` and `isCompactCheckpointSource()` are declared on the `@deepseek-ai/dsh-compact/checkpoint` subpath and re-exported from the root, so host-side consumers keep reading them from the root. The leaf imports no cordis and declares no module augmentation (the [`dsh-commands/brand`](../../ui/commands/README.md) shape), which is what lets a client or wire program name the checkpoint source: the package **root** cannot enter such a program at all, because it reaches `dsh-session`'s root and that `Context` merge declares the host `sessions` service against the client's own (`TS2717` — one program per side, per [development.md](../../../docs/development.md#typescript-project-layout)). The web client's transcript adapter pins its plugin literal to this leaf with a type-only import, so renaming the plugin id here is a compile error there. +`COMPACT_CHECKPOINT_SOURCE` and `isCompactCheckpointSource()` are declared on the `@deepseek-ai/dsh-compact/checkpoint` subpath and re-exported from the root, so host-side consumers keep reading them from the root. The leaf imports no cordis and declares no module augmentation (the [`dsh-commands/brand`](../../interaction/commands/README.md) shape), which is what lets a client or wire program name the checkpoint source: the package **root** cannot enter such a program at all, because it reaches `dsh-session`'s root and that `Context` merge declares the host `sessions` service against the client's own (`TS2717` — one program per side, per [development.md](../../../docs/development.md#typescript-project-layout)). The web client's transcript adapter pins its plugin literal to this leaf with a type-only import, so renaming the plugin id here is a compile error there. ## Model Experience diff --git a/packages/compact/compact/README.zh.md b/packages/compact/compact/README.zh.md index ed6ccc6301..434b9bfa3d 100644 --- a/packages/compact/compact/README.zh.md +++ b/packages/compact/compact/README.zh.md @@ -68,7 +68,7 @@ ## 在 host 程序之外识别检查点(`./checkpoint`) -`COMPACT_CHECKPOINT_SOURCE` 与 `isCompactCheckpointSource()` 声明在 `@deepseek-ai/dsh-compact/checkpoint` 子路径上,并由包根重新导出,因此 host 侧消费方仍从根读取它们。该叶子不导入 cordis、也不声明任何模块增强(即 [`dsh-commands/brand`](../../ui/commands/README.md) 的形状),这正是客户端或 wire 程序能够命名该检查点来源的原因:包的**根**根本无法进入这类程序,因为它会到达 `dsh-session` 的根,而那处 `Context` 合并会让 host 的 `sessions` 服务与客户端自己的冲突(`TS2717`——每侧一个程序,见 [development.md](../../../docs/development.md#typescript-project-layout))。Web 客户端的对话记录适配器用仅类型导入把它的插件字面量钉在该叶子上,因此在此处改插件 id 会让那边编译失败。 +`COMPACT_CHECKPOINT_SOURCE` 与 `isCompactCheckpointSource()` 声明在 `@deepseek-ai/dsh-compact/checkpoint` 子路径上,并由包根重新导出,因此 host 侧消费方仍从根读取它们。该叶子不导入 cordis、也不声明任何模块增强(即 [`dsh-commands/brand`](../../interaction/commands/README.md) 的形状),这正是客户端或 wire 程序能够命名该检查点来源的原因:包的**根**根本无法进入这类程序,因为它会到达 `dsh-session` 的根,而那处 `Context` 合并会让 host 的 `sessions` 服务与客户端自己的冲突(`TS2717`——每侧一个程序,见 [development.md](../../../docs/development.md#typescript-project-layout))。Web 客户端的对话记录适配器用仅类型导入把它的插件字面量钉在该叶子上,因此在此处改插件 id 会让那边编译失败。 ## 模型体验 diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index c1bd91ce2b..e8157eed7e 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/tools/README.md -README.md: 81cc57983d83fd19468017b217d4db9978f4e228 -README.zh.md: 9f875bd80a03d1d0f78625ee98eeaad9d118f871 +README.md: fd5cb85540e42e62de28f96bf59890da9561f51e +README.zh.md: 5911b25b1d77710077063a643a29e500e4da8e95 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 81cc57983d..fd5cb85540 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -45,7 +45,7 @@ The live registry pipeline has three transformable waterfalls, then the definiti - `ToolExecution` — the readonly pipeline view: immutable `{ token, callId, name, arguments, signal, agent?, parent? }`; the registry separately retains and re-fuses the original caller signal. `ToolDispatchExecution` is the `tools/execute`-only view whose required signal is mutable, so a wrapper may replace and restore it but cannot delete it. A nested call's `parent` is a `ToolExecutionToken`, not an execution object. - `ToolRunContext` — the execution passed to a tool body, extending `ToolExecution` with `deferContext(context)`. It defers one context until the tool's final result reaches the loop — typically a nested-dispatch context ferried by a composite tool, or a fresh plugin-sourced instruction minted by a leaf tool (`tool-goal`'s wrap-up) — even when the tool later throws or cancellation wins; it never injects immediately. - `ToolExecutionResult` — discriminated execution-local outcome. Success is `{ isError:false, value:JsonValue, content, meta?, additionalContexts? }`; failure is `{ isError:true, error:{ message, info? }, content, meta?, additionalContexts? }` and has no value. Call identity stays on the immutable `ToolExecution`. The registry snapshots, validates, and freezes the canonical value before rendering, then materializes the durable presentation fields before final observation. `ToolFailure.info` carries an internal `{ name, code }` for a `HarnessError`; `additionalContexts` preserves every deferred or post-execute identified `UserMessage` for the loop's post-result FIFO. -- `PreToolDecision` — `{kind:'allow'}` | `{kind:'deny', reason}` | `{kind:'ask', reason?}`. Input rewrite is deliberately not offered; `ask` is serviced by [`ctx.approval`](../../ui/user-approval/README.md) when mounted and otherwise degrades to deny. +- `PreToolDecision` — `{kind:'allow'}` | `{kind:'deny', reason}` | `{kind:'ask', reason?}`. Input rewrite is deliberately not offered; `ask` is serviced by [`ctx.approval`](../../interaction/user-approval/README.md) when mounted and otherwise degrades to deny. - `PostToolDecision` — accept may replace `content` or `value`, never both, and may attach `additionalContexts`; block turns feedback into a valueless failure. Content replacement preserves the canonical value and metadata. Value replacement is revalidated and rerenders content/metadata. Accept preserves tool-deferred contexts before decision contexts; block discards tool-deferred contexts and exposes only contexts explicitly supplied by the blocking decision. - `ToolGuard` — `(execution) => string | undefined`; the returned string is a final monotonic denial reason evaluated after the reorderable pre-execute waterfall and before dispatch. - `ToolCallView` / `ToolResultView` — provider-neutral `card`-tagged render intents a tool returns from `presentCall` / `presentResult` to own how a UI renders ITS calls (see "Tool-owned UI presentation"). diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index 9f875bd80a..5911b25b1d 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -45,7 +45,7 @@ tools: - `ToolExecution`:只读流水线视图:不可变的 `{ token, callId, name, arguments, signal, agent?, parent? }`;注册表会另行保留并重新融合调用方的原始信号。`ToolDispatchExecution` 是仅供 `tools/execute` 使用的视图,其必填信号可变,因此包装层可以替换并还原它,但不能删除它。嵌套调用的 `parent` 是 `ToolExecutionToken`,而不是执行对象。 - `ToolRunContext`:传给工具主体的执行上下文,在 `ToolExecution` 基础上增加 `deferContext(context)`。它把一条上下文推迟到该工具的最终结果抵达循环时——通常是组合工具转运的嵌套分发上下文,也可以是叶子工具铸造的全新插件来源指令(如 `tool-goal` 的收尾注入)——即使工具后来抛出或取消胜出也不例外;该方法绝不会立即注入上下文。 - `ToolExecutionResult`:可辨识的执行局部结果。成功形态为 `{ isError:false, value:JsonValue, content, meta?, additionalContexts? }`;失败形态为 `{ isError:true, error:{ message, info? }, content, meta?, additionalContexts? }`,且不含值。调用身份保留在不可变的 `ToolExecution` 上。注册表会在呈现前快照、验证并冻结规范值,随后在最终观测前实体化持久呈现字段。`ToolFailure.info` 携带内部的 `{ name, code }`,用于表示 `HarnessError`;`additionalContexts` 会保留每个通过延迟或 post-execute 加入且带标识的 `UserMessage`,供循环在结果后按 FIFO 顺序处理。 -- `PreToolDecision`:`{kind:'allow'}` | `{kind:'deny', reason}` | `{kind:'ask', reason?}`。该类型有意不提供输入改写;`ask` 在挂载 [`ctx.approval`](../../ui/user-approval/README.md) 时由它处理,否则退化为拒绝。 +- `PreToolDecision`:`{kind:'allow'}` | `{kind:'deny', reason}` | `{kind:'ask', reason?}`。该类型有意不提供输入改写;`ask` 在挂载 [`ctx.approval`](../../interaction/user-approval/README.md) 时由它处理,否则退化为拒绝。 - `PostToolDecision`:接受决定可以替换 `content` 或 `value`(不能同时替换),并可附加 `additionalContexts`;阻止决定会把反馈变成无值失败。替换内容会保留规范值和元数据。替换值会重新验证,并重新呈现内容/元数据。接受决定会先保留工具延迟的上下文,再附加决定上下文;阻止决定会丢弃工具延迟的上下文,只公开阻止决定显式提供的上下文。 - `ToolGuard`:`(execution) => string | undefined`;返回的字符串是最终单调拒绝理由,在可重排的前置执行 waterfall 之后、分发之前求值。 - `ToolCallView` / `ToolResultView`:提供方无关、带 `card` 标签的呈现意图;工具通过 `presentCall` / `presentResult` 返回该意图,从而拥有 UI 呈现其自身调用的方式(参见「工具拥有的 UI 呈现」)。 diff --git a/packages/core/tools/tsconfig.json b/packages/core/tools/tsconfig.json index 918112d7d0..de5756ecbd 100644 --- a/packages/core/tools/tsconfig.json +++ b/packages/core/tools/tsconfig.json @@ -33,7 +33,7 @@ "path": "../../core/scope" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" }, { "path": "../../support/invariants" diff --git a/packages/examples/acp-demo/tests/built-bin.e2e.ts b/packages/examples/acp-demo/tests/built-bin.e2e.ts index d09a0e333d..14b2c3f607 100644 --- a/packages/examples/acp-demo/tests/built-bin.e2e.ts +++ b/packages/examples/acp-demo/tests/built-bin.e2e.ts @@ -33,7 +33,7 @@ const decompress = promisify(zstdDecompress) const dshPackages = [ 'examples/agent-spine-demo', 'core/agent', 'core/session', 'core/system-prompt', 'core/tools', 'core/agent-loop', 'llm/llm', 'bash/bash', - 'bash/bash-local', 'bash/tool-bash', 'subprocess/subprocess', 'subprocess/subprocess-local', 'context/workspace-context', 'support/invariants', 'ui/app-boot', + 'bash/bash-local', 'bash/tool-bash', 'subprocess/subprocess', 'subprocess/subprocess-local', 'context/workspace-context', 'support/invariants', 'boot/app-boot', 'session/session-persistence', 'session/session-checkpoint-policy', 'session/session-persistence-jsonl', 'acp/acp', 'examples/acp-demo', 'util/paths', diff --git a/packages/examples/acp-demo/tsconfig.json b/packages/examples/acp-demo/tsconfig.json index 34b48fc46a..5c7193f1bb 100644 --- a/packages/examples/acp-demo/tsconfig.json +++ b/packages/examples/acp-demo/tsconfig.json @@ -18,7 +18,7 @@ "path": "../../../vendor/loader" }, { - "path": "../../ui/app-boot" + "path": "../../boot/app-boot" }, { "path": "../../acp/acp" diff --git a/packages/examples/jsonrpc-demo/tsconfig.json b/packages/examples/jsonrpc-demo/tsconfig.json index aba279d405..061bbf68c3 100644 --- a/packages/examples/jsonrpc-demo/tsconfig.json +++ b/packages/examples/jsonrpc-demo/tsconfig.json @@ -15,7 +15,7 @@ "path": "../../../vendor/loader" }, { - "path": "../../ui/app-boot" + "path": "../../boot/app-boot" }, { "path": "../../support/invariants" diff --git a/packages/fs/tool-fs/tsconfig.json b/packages/fs/tool-fs/tsconfig.json index fb420b553c..a3f128143c 100644 --- a/packages/fs/tool-fs/tsconfig.json +++ b/packages/fs/tool-fs/tsconfig.json @@ -40,7 +40,7 @@ "path": "../../sandbox/sandbox-policy" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" } ] } diff --git a/packages/goal/command-goal/tsconfig.json b/packages/goal/command-goal/tsconfig.json index 8b03235814..488c576922 100644 --- a/packages/goal/command-goal/tsconfig.json +++ b/packages/goal/command-goal/tsconfig.json @@ -15,7 +15,7 @@ "path": "../../../vendor/cordis" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../goal" diff --git a/packages/host/apiproxy/tsconfig.json b/packages/host/apiproxy/tsconfig.json index 58c63f5669..64d7944892 100644 --- a/packages/host/apiproxy/tsconfig.json +++ b/packages/host/apiproxy/tsconfig.json @@ -66,13 +66,13 @@ "path": "../../skill/skill" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" }, { - "path": "../../ui/user-interaction" + "path": "../../interaction/user-interaction" }, { "path": "../../workspace/workspace" diff --git a/packages/interaction/README.i18n.yaml b/packages/interaction/README.i18n.yaml new file mode 100644 index 0000000000..2555e896a1 --- /dev/null +++ b/packages/interaction/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/interaction/README.md +README.md: 506b47753fc9250f9f7bd971265421ad46a106b2 +README.zh.md: 34d336f3d3c10deab166d5c13bb86c13ee9bced0 diff --git a/packages/ui/README.md b/packages/interaction/README.md similarity index 71% rename from packages/ui/README.md rename to packages/interaction/README.md index 15754410a4..506b47753f 100644 --- a/packages/ui/README.md +++ b/packages/interaction/README.md @@ -1,8 +1,8 @@ -# ui/ — human and SDK-client integration surfaces +# interaction/ — the human-collaboration plane English | [中文](README.zh.md) -Human-facing channels and the out-of-process SDK server. These are **product** packages: real interfaces that a person or SDK client drives. +The seams through which a human collaborates with a running agent — questions, approvals, permission presets, commands. These are **product** packages: real interfaces a person drives. | Package | Role | ctx key | |---|---|---| @@ -11,7 +11,5 @@ Human-facing channels and the out-of-process SDK server. These are **product** p | [`permission/`](permission/README.md) | Presents and persists user-facing permission presets. | `ctx.permission` | | [`user-interaction/`](user-interaction/README.md) | Defines the provider-neutral human question/answer seam. | `ctx.userInteraction` | | [`tool-ask-user/`](tool-ask-user/README.md) | Exposes human questions to the model. | (registers on `ctx.tools`) | -| [`jsonrpc/`](jsonrpc/README.md) | Serves out-of-process SDK clients over stdio JSON-RPC. | (drives `ctx.agents`) | -| [`app-boot/`](app-boot/README.md) | Provides shared boot support for application launchers. | (library for the bins) | These packages integrate through existing agent and session contracts rather than changing the loop. Interactive applications provide the concrete command, approval, and question adapters; automation uses [`acp/`](../acp/README.md), and runnable demo bundles live under [`examples/`](../examples/README.md). The product [`dsh`](../../apps/cli/README.md) CLI composes these packages directly. diff --git a/packages/ui/README.zh.md b/packages/interaction/README.zh.md similarity index 69% rename from packages/ui/README.zh.md rename to packages/interaction/README.zh.md index 4023b80085..34d336f3d3 100644 --- a/packages/ui/README.zh.md +++ b/packages/interaction/README.zh.md @@ -1,8 +1,8 @@ -# ui/:面向用户和 SDK 客户端的集成接口 +# interaction/:人机协作平面 [English](README.md) | 中文 -面向用户的通道和进程外 SDK 服务器。这些是**产品**包:由用户或 SDK 客户端直接操作的真实接口。 +人与运行中的 agent(智能体)协作所经由的各个 seam——提问、审批、权限预设、命令。这些是**产品**包(package):由用户直接操作的真实接口。 | 包 | 职责 | ctx 键 | |---|---|---| @@ -11,7 +11,5 @@ | [`permission/`](permission/README.md) | 呈现并持久化面向用户的权限预设。 | `ctx.permission` | | [`user-interaction/`](user-interaction/README.md) | 定义与提供方无关的用户问答 seam。 | `ctx.userInteraction` | | [`tool-ask-user/`](tool-ask-user/README.md) | 向模型公开用户问题。 | (注册到 `ctx.tools`) | -| [`jsonrpc/`](jsonrpc/README.md) | 通过 stdio JSON-RPC 为进程外 SDK 客户端提供服务。 | (驱动 `ctx.agents`) | -| [`app-boot/`](app-boot/README.md) | 为应用启动器提供共享启动支持。 | (供各 bin 使用的库) | 这些包通过现有的 agent(智能体)和会话契约集成,而不改变循环。交互式应用提供具体的命令、审批和提问适配器;自动化使用 [`acp/`](../acp/README.md),可运行的演示组合包位于 [`examples/`](../examples/README.md)。产品 [`dsh`](../../apps/cli/README.md) CLI(命令行界面)直接组合这些包。 diff --git a/packages/ui/commands/README.i18n.yaml b/packages/interaction/commands/README.i18n.yaml similarity index 79% rename from packages/ui/commands/README.i18n.yaml rename to packages/interaction/commands/README.i18n.yaml index be55a19ca3..967fa0d4ef 100644 --- a/packages/ui/commands/README.i18n.yaml +++ b/packages/interaction/commands/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/commands/README.md +# pnpm run verify-translation-pairing --write packages/interaction/commands/README.md README.md: 1709bdcdce4e43d98cfea5ff3972ab95bfd3c33b README.zh.md: 569f2aa8293793b26d63ee16e3ea7600e04a8397 diff --git a/packages/ui/commands/README.md b/packages/interaction/commands/README.md similarity index 100% rename from packages/ui/commands/README.md rename to packages/interaction/commands/README.md diff --git a/packages/ui/commands/README.zh.md b/packages/interaction/commands/README.zh.md similarity index 100% rename from packages/ui/commands/README.zh.md rename to packages/interaction/commands/README.zh.md diff --git a/packages/ui/commands/package.json b/packages/interaction/commands/package.json similarity index 100% rename from packages/ui/commands/package.json rename to packages/interaction/commands/package.json diff --git a/packages/ui/commands/src/brand.ts b/packages/interaction/commands/src/brand.ts similarity index 100% rename from packages/ui/commands/src/brand.ts rename to packages/interaction/commands/src/brand.ts diff --git a/packages/ui/commands/src/index.ts b/packages/interaction/commands/src/index.ts similarity index 100% rename from packages/ui/commands/src/index.ts rename to packages/interaction/commands/src/index.ts diff --git a/packages/ui/commands/src/invariant.ts b/packages/interaction/commands/src/invariant.ts similarity index 100% rename from packages/ui/commands/src/invariant.ts rename to packages/interaction/commands/src/invariant.ts diff --git a/packages/ui/commands/tests/commands.spec.ts b/packages/interaction/commands/tests/commands.spec.ts similarity index 100% rename from packages/ui/commands/tests/commands.spec.ts rename to packages/interaction/commands/tests/commands.spec.ts diff --git a/packages/ui/commands/tests/invariant.spec.ts b/packages/interaction/commands/tests/invariant.spec.ts similarity index 100% rename from packages/ui/commands/tests/invariant.spec.ts rename to packages/interaction/commands/tests/invariant.spec.ts diff --git a/packages/ui/commands/tsconfig.json b/packages/interaction/commands/tsconfig.json similarity index 100% rename from packages/ui/commands/tsconfig.json rename to packages/interaction/commands/tsconfig.json diff --git a/packages/interaction/permission/README.i18n.yaml b/packages/interaction/permission/README.i18n.yaml new file mode 100644 index 0000000000..b5883174dc --- /dev/null +++ b/packages/interaction/permission/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/interaction/permission/README.md +README.md: 814085ed6f2c9650854f377e1c97e442fc4211a4 +README.zh.md: 36880d6b8c3f0b39b88db1abb02534f30e3355fa diff --git a/packages/ui/permission/README.md b/packages/interaction/permission/README.md similarity index 100% rename from packages/ui/permission/README.md rename to packages/interaction/permission/README.md diff --git a/packages/ui/permission/README.zh.md b/packages/interaction/permission/README.zh.md similarity index 100% rename from packages/ui/permission/README.zh.md rename to packages/interaction/permission/README.zh.md diff --git a/packages/ui/permission/package.json b/packages/interaction/permission/package.json similarity index 100% rename from packages/ui/permission/package.json rename to packages/interaction/permission/package.json diff --git a/packages/ui/permission/src/client.ts b/packages/interaction/permission/src/client.ts similarity index 100% rename from packages/ui/permission/src/client.ts rename to packages/interaction/permission/src/client.ts diff --git a/packages/ui/permission/src/index.ts b/packages/interaction/permission/src/index.ts similarity index 100% rename from packages/ui/permission/src/index.ts rename to packages/interaction/permission/src/index.ts diff --git a/packages/ui/permission/src/invariant.ts b/packages/interaction/permission/src/invariant.ts similarity index 100% rename from packages/ui/permission/src/invariant.ts rename to packages/interaction/permission/src/invariant.ts diff --git a/packages/ui/permission/src/types.ts b/packages/interaction/permission/src/types.ts similarity index 100% rename from packages/ui/permission/src/types.ts rename to packages/interaction/permission/src/types.ts diff --git a/packages/ui/permission/tests/invariant.spec.ts b/packages/interaction/permission/tests/invariant.spec.ts similarity index 100% rename from packages/ui/permission/tests/invariant.spec.ts rename to packages/interaction/permission/tests/invariant.spec.ts diff --git a/packages/ui/permission/tests/permission.spec.ts b/packages/interaction/permission/tests/permission.spec.ts similarity index 100% rename from packages/ui/permission/tests/permission.spec.ts rename to packages/interaction/permission/tests/permission.spec.ts diff --git a/packages/ui/permission/tests/projection.spec.ts b/packages/interaction/permission/tests/projection.spec.ts similarity index 100% rename from packages/ui/permission/tests/projection.spec.ts rename to packages/interaction/permission/tests/projection.spec.ts diff --git a/packages/ui/permission/tsconfig.json b/packages/interaction/permission/tsconfig.json similarity index 100% rename from packages/ui/permission/tsconfig.json rename to packages/interaction/permission/tsconfig.json diff --git a/packages/ui/tool-ask-user/README.i18n.yaml b/packages/interaction/tool-ask-user/README.i18n.yaml similarity index 79% rename from packages/ui/tool-ask-user/README.i18n.yaml rename to packages/interaction/tool-ask-user/README.i18n.yaml index bbd0ba0561..f88c303525 100644 --- a/packages/ui/tool-ask-user/README.i18n.yaml +++ b/packages/interaction/tool-ask-user/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/tool-ask-user/README.md +# pnpm run verify-translation-pairing --write packages/interaction/tool-ask-user/README.md README.md: 7af356263ea6582a081e7c6de22fd317ca8b96df README.zh.md: 3f7b814b83c8c6957a4b2574ee69e87d45f65ae6 diff --git a/packages/ui/tool-ask-user/README.md b/packages/interaction/tool-ask-user/README.md similarity index 100% rename from packages/ui/tool-ask-user/README.md rename to packages/interaction/tool-ask-user/README.md diff --git a/packages/ui/tool-ask-user/README.zh.md b/packages/interaction/tool-ask-user/README.zh.md similarity index 100% rename from packages/ui/tool-ask-user/README.zh.md rename to packages/interaction/tool-ask-user/README.zh.md diff --git a/packages/ui/tool-ask-user/package.json b/packages/interaction/tool-ask-user/package.json similarity index 100% rename from packages/ui/tool-ask-user/package.json rename to packages/interaction/tool-ask-user/package.json diff --git a/packages/ui/tool-ask-user/src/index.ts b/packages/interaction/tool-ask-user/src/index.ts similarity index 100% rename from packages/ui/tool-ask-user/src/index.ts rename to packages/interaction/tool-ask-user/src/index.ts diff --git a/packages/ui/tool-ask-user/src/invariant.ts b/packages/interaction/tool-ask-user/src/invariant.ts similarity index 100% rename from packages/ui/tool-ask-user/src/invariant.ts rename to packages/interaction/tool-ask-user/src/invariant.ts diff --git a/packages/ui/tool-ask-user/tests/tool-ask-user.spec.ts b/packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts similarity index 100% rename from packages/ui/tool-ask-user/tests/tool-ask-user.spec.ts rename to packages/interaction/tool-ask-user/tests/tool-ask-user.spec.ts diff --git a/packages/ui/tool-ask-user/tsconfig.json b/packages/interaction/tool-ask-user/tsconfig.json similarity index 100% rename from packages/ui/tool-ask-user/tsconfig.json rename to packages/interaction/tool-ask-user/tsconfig.json diff --git a/packages/ui/user-approval/README.i18n.yaml b/packages/interaction/user-approval/README.i18n.yaml similarity index 79% rename from packages/ui/user-approval/README.i18n.yaml rename to packages/interaction/user-approval/README.i18n.yaml index cf9c8e1fce..36188334e7 100644 --- a/packages/ui/user-approval/README.i18n.yaml +++ b/packages/interaction/user-approval/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/user-approval/README.md +# pnpm run verify-translation-pairing --write packages/interaction/user-approval/README.md README.md: 7b87a75d1c7c43874c484bc11f8deed45cb523ce README.zh.md: 3c02f8d673a62c3ee954da26f6341b433032a88c diff --git a/packages/ui/user-approval/README.md b/packages/interaction/user-approval/README.md similarity index 100% rename from packages/ui/user-approval/README.md rename to packages/interaction/user-approval/README.md diff --git a/packages/ui/user-approval/README.zh.md b/packages/interaction/user-approval/README.zh.md similarity index 100% rename from packages/ui/user-approval/README.zh.md rename to packages/interaction/user-approval/README.zh.md diff --git a/packages/ui/user-approval/package.json b/packages/interaction/user-approval/package.json similarity index 100% rename from packages/ui/user-approval/package.json rename to packages/interaction/user-approval/package.json diff --git a/packages/ui/user-approval/src/index.ts b/packages/interaction/user-approval/src/index.ts similarity index 100% rename from packages/ui/user-approval/src/index.ts rename to packages/interaction/user-approval/src/index.ts diff --git a/packages/ui/user-approval/src/invariant.ts b/packages/interaction/user-approval/src/invariant.ts similarity index 100% rename from packages/ui/user-approval/src/invariant.ts rename to packages/interaction/user-approval/src/invariant.ts diff --git a/packages/ui/user-approval/src/types.ts b/packages/interaction/user-approval/src/types.ts similarity index 100% rename from packages/ui/user-approval/src/types.ts rename to packages/interaction/user-approval/src/types.ts diff --git a/packages/ui/user-approval/tests/approval.spec.ts b/packages/interaction/user-approval/tests/approval.spec.ts similarity index 100% rename from packages/ui/user-approval/tests/approval.spec.ts rename to packages/interaction/user-approval/tests/approval.spec.ts diff --git a/packages/ui/user-approval/tests/invariant.spec.ts b/packages/interaction/user-approval/tests/invariant.spec.ts similarity index 100% rename from packages/ui/user-approval/tests/invariant.spec.ts rename to packages/interaction/user-approval/tests/invariant.spec.ts diff --git a/packages/ui/user-approval/tsconfig.json b/packages/interaction/user-approval/tsconfig.json similarity index 100% rename from packages/ui/user-approval/tsconfig.json rename to packages/interaction/user-approval/tsconfig.json diff --git a/packages/ui/user-approval/tsdown.config.ts b/packages/interaction/user-approval/tsdown.config.ts similarity index 100% rename from packages/ui/user-approval/tsdown.config.ts rename to packages/interaction/user-approval/tsdown.config.ts diff --git a/packages/interaction/user-interaction/README.i18n.yaml b/packages/interaction/user-interaction/README.i18n.yaml new file mode 100644 index 0000000000..f467aaf4e1 --- /dev/null +++ b/packages/interaction/user-interaction/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/interaction/user-interaction/README.md +README.md: d234d6677bdd772f1bbd2c979c0d41f90aef5c32 +README.zh.md: c89210b6955a661313ca9e0e82e43da5a4d1db79 diff --git a/packages/ui/user-interaction/README.md b/packages/interaction/user-interaction/README.md similarity index 100% rename from packages/ui/user-interaction/README.md rename to packages/interaction/user-interaction/README.md diff --git a/packages/ui/user-interaction/README.zh.md b/packages/interaction/user-interaction/README.zh.md similarity index 100% rename from packages/ui/user-interaction/README.zh.md rename to packages/interaction/user-interaction/README.zh.md diff --git a/packages/ui/user-interaction/package.json b/packages/interaction/user-interaction/package.json similarity index 100% rename from packages/ui/user-interaction/package.json rename to packages/interaction/user-interaction/package.json diff --git a/packages/ui/user-interaction/src/index.ts b/packages/interaction/user-interaction/src/index.ts similarity index 100% rename from packages/ui/user-interaction/src/index.ts rename to packages/interaction/user-interaction/src/index.ts diff --git a/packages/ui/user-interaction/src/invariant.ts b/packages/interaction/user-interaction/src/invariant.ts similarity index 100% rename from packages/ui/user-interaction/src/invariant.ts rename to packages/interaction/user-interaction/src/invariant.ts diff --git a/packages/ui/user-interaction/src/types.ts b/packages/interaction/user-interaction/src/types.ts similarity index 100% rename from packages/ui/user-interaction/src/types.ts rename to packages/interaction/user-interaction/src/types.ts diff --git a/packages/ui/user-interaction/tests/user-interaction.spec.ts b/packages/interaction/user-interaction/tests/user-interaction.spec.ts similarity index 100% rename from packages/ui/user-interaction/tests/user-interaction.spec.ts rename to packages/interaction/user-interaction/tests/user-interaction.spec.ts diff --git a/packages/ui/user-interaction/tsconfig.json b/packages/interaction/user-interaction/tsconfig.json similarity index 100% rename from packages/ui/user-interaction/tsconfig.json rename to packages/interaction/user-interaction/tsconfig.json diff --git a/packages/plan/plan-mode/tsconfig.json b/packages/plan/plan-mode/tsconfig.json index 6fa3cffbbc..4dde8fda45 100644 --- a/packages/plan/plan-mode/tsconfig.json +++ b/packages/plan/plan-mode/tsconfig.json @@ -27,10 +27,10 @@ "path": "../../core/system-prompt" }, { - "path": "../../ui/user-interaction" + "path": "../../interaction/user-interaction" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../../session/session-projection" diff --git a/packages/ui/README.i18n.yaml b/packages/scaffold/README.i18n.yaml similarity index 57% rename from packages/ui/README.i18n.yaml rename to packages/scaffold/README.i18n.yaml index fba1f95c52..1480a14b69 100644 --- a/packages/ui/README.i18n.yaml +++ b/packages/scaffold/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/README.md -README.md: 15754410a4a81eb3fc898dd55269ddd1637e1dab -README.zh.md: 4023b80085998f57ed321bfda3a0abdd08b70a28 +# pnpm run verify-translation-pairing --write packages/scaffold/README.md +README.md: b166a351a53afe075ecb794fe2bf6f0702a435ae +README.zh.md: 6e2a65ea554487f48563c16e1defa37ef1a5e043 diff --git a/packages/sdk/README.md b/packages/scaffold/README.md similarity index 64% rename from packages/sdk/README.md rename to packages/scaffold/README.md index 3f99d6d45d..b166a351a5 100644 --- a/packages/sdk/README.md +++ b/packages/scaffold/README.md @@ -1,16 +1,17 @@ -# SDK packages +# scaffold/ — create, launch, and drive projects from outside English | [中文](README.zh.md) -This group contains developer tooling for Harness projects and the client stack for driving a Harness runtime from another process. +This group contains developer tooling for Harness projects and the client stack for driving a Harness runtime from another process. Folders are role-named without the legacy `sdk-` prefix; npm names converge on `dsh-sdk-*` through the FIXME-tracked renames in the [regrouping Agent Note](../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md). | Package | Role | |---|---| | [`helper/`](helper/README.md) | Provides the shared project-editing domain | | [`scripts/`](scripts/README.md) | Provides the `dsh-sdk` project commands | | [`create-sdk/`](create-sdk/README.md) | Creates new SDK projects | -| [`sdk-protocol/`](sdk-protocol/README.md) | Defines the SDK runtime wire protocol | -| [`sdk-client/`](sdk-client/README.md) | Drives a Harness runtime through the TypeScript client API | +| [`protocol/`](protocol/README.md) | Defines the SDK runtime wire protocol | +| [`client/`](client/README.md) | Drives a Harness runtime through the TypeScript client API | +| [`server/`](server/README.md) | Serves out-of-process SDK clients over stdio JSON-RPC | | [`telemetry/`](telemetry/README.md) | Provides launcher telemetry, consent, and redaction primitives | `@deepseek-ai/create-sdk` follows npm's scoped initializer naming convention; the other packages follow the repository's `@deepseek-ai/dsh-*` convention. See the [developer-project workflow](../../.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md), [project-editing architecture](../../.agents/notes/proposed/architecture/2026-07-15-sdk-project-editing-architecture.md), and [TypeScript SDK design](../../.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md). diff --git a/packages/scaffold/README.zh.md b/packages/scaffold/README.zh.md new file mode 100644 index 0000000000..6e2a65ea55 --- /dev/null +++ b/packages/scaffold/README.zh.md @@ -0,0 +1,17 @@ +# scaffold/:从外部创建、启动、驱动项目 + +[English](README.md) | 中文 + +本组包含 Harness 项目的开发者工具,以及从另一进程驱动 Harness 运行时的客户端栈。目录按角色命名,不带遗留的 `sdk-` 前缀;npm 名则经由[重新分组 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)中 FIXME 跟踪的改名收敛为 `dsh-sdk-*`。 + +| 包 | 职责 | +|---|---| +| [`helper/`](helper/README.md) | 提供共享的项目编辑领域 | +| [`scripts/`](scripts/README.md) | 提供 `dsh-sdk` 项目命令 | +| [`create-sdk/`](create-sdk/README.md) | 创建新的 SDK 项目 | +| [`protocol/`](protocol/README.md) | 定义 SDK 运行时通信协议 | +| [`client/`](client/README.md) | 通过 TypeScript 客户端 API 驱动 Harness 运行时 | +| [`server/`](server/README.md) | 通过 stdio JSON-RPC 为进程外 SDK 客户端提供服务 | +| [`telemetry/`](telemetry/README.md) | 提供启动器 telemetry、同意与脱敏原语 | + +`@deepseek-ai/create-sdk` 遵循 npm 的 scoped initializer 命名约定;其余包遵循仓库的 `@deepseek-ai/dsh-*` 约定。参见[开发者项目工作流](../../.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md)、[项目编辑架构](../../.agents/notes/proposed/architecture/2026-07-15-sdk-project-editing-architecture.md)与 [TypeScript SDK 设计](../../.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md)。 diff --git a/packages/sdk/sdk-client/README.i18n.yaml b/packages/scaffold/client/README.i18n.yaml similarity index 80% rename from packages/sdk/sdk-client/README.i18n.yaml rename to packages/scaffold/client/README.i18n.yaml index bff82b8aec..0a71141cd6 100644 --- a/packages/sdk/sdk-client/README.i18n.yaml +++ b/packages/scaffold/client/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/sdk-client/README.md +# pnpm run verify-translation-pairing --write packages/scaffold/client/README.md README.md: f27ec256330254156c45b136c21308529ea99e3d README.zh.md: bd23762500a4da469839da8ddb50455e3419b553 diff --git a/packages/sdk/sdk-client/README.md b/packages/scaffold/client/README.md similarity index 100% rename from packages/sdk/sdk-client/README.md rename to packages/scaffold/client/README.md diff --git a/packages/sdk/sdk-client/README.zh.md b/packages/scaffold/client/README.zh.md similarity index 100% rename from packages/sdk/sdk-client/README.zh.md rename to packages/scaffold/client/README.zh.md diff --git a/packages/sdk/sdk-client/package.json b/packages/scaffold/client/package.json similarity index 100% rename from packages/sdk/sdk-client/package.json rename to packages/scaffold/client/package.json diff --git a/packages/sdk/sdk-client/src/api.ts b/packages/scaffold/client/src/api.ts similarity index 100% rename from packages/sdk/sdk-client/src/api.ts rename to packages/scaffold/client/src/api.ts diff --git a/packages/sdk/sdk-client/src/client.ts b/packages/scaffold/client/src/client.ts similarity index 100% rename from packages/sdk/sdk-client/src/client.ts rename to packages/scaffold/client/src/client.ts diff --git a/packages/sdk/sdk-client/src/dispose.ts b/packages/scaffold/client/src/dispose.ts similarity index 100% rename from packages/sdk/sdk-client/src/dispose.ts rename to packages/scaffold/client/src/dispose.ts diff --git a/packages/sdk/sdk-client/src/index.ts b/packages/scaffold/client/src/index.ts similarity index 100% rename from packages/sdk/sdk-client/src/index.ts rename to packages/scaffold/client/src/index.ts diff --git a/packages/sdk/sdk-client/src/invariant.ts b/packages/scaffold/client/src/invariant.ts similarity index 100% rename from packages/sdk/sdk-client/src/invariant.ts rename to packages/scaffold/client/src/invariant.ts diff --git a/packages/sdk/sdk-client/src/types.ts b/packages/scaffold/client/src/types.ts similarity index 100% rename from packages/sdk/sdk-client/src/types.ts rename to packages/scaffold/client/src/types.ts diff --git a/packages/sdk/sdk-client/tests/dispose.spec.ts b/packages/scaffold/client/tests/dispose.spec.ts similarity index 100% rename from packages/sdk/sdk-client/tests/dispose.spec.ts rename to packages/scaffold/client/tests/dispose.spec.ts diff --git a/packages/sdk/sdk-client/tests/fake-runtime.ts b/packages/scaffold/client/tests/fake-runtime.ts similarity index 100% rename from packages/sdk/sdk-client/tests/fake-runtime.ts rename to packages/scaffold/client/tests/fake-runtime.ts diff --git a/packages/sdk/sdk-client/tests/sdk-client.spec.ts b/packages/scaffold/client/tests/sdk-client.spec.ts similarity index 100% rename from packages/sdk/sdk-client/tests/sdk-client.spec.ts rename to packages/scaffold/client/tests/sdk-client.spec.ts diff --git a/packages/sdk/sdk-client/tsconfig.json b/packages/scaffold/client/tsconfig.json similarity index 93% rename from packages/sdk/sdk-client/tsconfig.json rename to packages/scaffold/client/tsconfig.json index 567c6582bb..a5456f5ad3 100644 --- a/packages/sdk/sdk-client/tsconfig.json +++ b/packages/scaffold/client/tsconfig.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../sdk-protocol" + "path": "../protocol" }, { "path": "../../support/invariants" diff --git a/packages/sdk/create-sdk/README.i18n.yaml b/packages/scaffold/create-sdk/README.i18n.yaml similarity index 80% rename from packages/sdk/create-sdk/README.i18n.yaml rename to packages/scaffold/create-sdk/README.i18n.yaml index 27609d9a11..62d5bd3793 100644 --- a/packages/sdk/create-sdk/README.i18n.yaml +++ b/packages/scaffold/create-sdk/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/create-sdk/README.md +# pnpm run verify-translation-pairing --write packages/scaffold/create-sdk/README.md README.md: aa09236832a50abdcd2b158e0561db26bce19cf6 README.zh.md: a4685e66bc3eb7924dd247a7b6b420f6bcc70990 diff --git a/packages/sdk/create-sdk/README.md b/packages/scaffold/create-sdk/README.md similarity index 100% rename from packages/sdk/create-sdk/README.md rename to packages/scaffold/create-sdk/README.md diff --git a/packages/sdk/create-sdk/README.zh.md b/packages/scaffold/create-sdk/README.zh.md similarity index 100% rename from packages/sdk/create-sdk/README.zh.md rename to packages/scaffold/create-sdk/README.zh.md diff --git a/packages/sdk/create-sdk/package.json b/packages/scaffold/create-sdk/package.json similarity index 100% rename from packages/sdk/create-sdk/package.json rename to packages/scaffold/create-sdk/package.json diff --git a/packages/sdk/create-sdk/src/args.ts b/packages/scaffold/create-sdk/src/args.ts similarity index 100% rename from packages/sdk/create-sdk/src/args.ts rename to packages/scaffold/create-sdk/src/args.ts diff --git a/packages/sdk/create-sdk/src/bin.ts b/packages/scaffold/create-sdk/src/bin.ts similarity index 100% rename from packages/sdk/create-sdk/src/bin.ts rename to packages/scaffold/create-sdk/src/bin.ts diff --git a/packages/sdk/create-sdk/src/command.ts b/packages/scaffold/create-sdk/src/command.ts similarity index 100% rename from packages/sdk/create-sdk/src/command.ts rename to packages/scaffold/create-sdk/src/command.ts diff --git a/packages/sdk/create-sdk/src/create-questions.ts b/packages/scaffold/create-sdk/src/create-questions.ts similarity index 100% rename from packages/sdk/create-sdk/src/create-questions.ts rename to packages/scaffold/create-sdk/src/create-questions.ts diff --git a/packages/sdk/create-sdk/src/create-wizard.ts b/packages/scaffold/create-sdk/src/create-wizard.ts similarity index 100% rename from packages/sdk/create-sdk/src/create-wizard.ts rename to packages/scaffold/create-sdk/src/create-wizard.ts diff --git a/packages/sdk/create-sdk/src/headless.ts b/packages/scaffold/create-sdk/src/headless.ts similarity index 100% rename from packages/sdk/create-sdk/src/headless.ts rename to packages/scaffold/create-sdk/src/headless.ts diff --git a/packages/sdk/create-sdk/src/index.ts b/packages/scaffold/create-sdk/src/index.ts similarity index 100% rename from packages/sdk/create-sdk/src/index.ts rename to packages/scaffold/create-sdk/src/index.ts diff --git a/packages/sdk/create-sdk/src/invariant.ts b/packages/scaffold/create-sdk/src/invariant.ts similarity index 100% rename from packages/sdk/create-sdk/src/invariant.ts rename to packages/scaffold/create-sdk/src/invariant.ts diff --git a/packages/sdk/create-sdk/src/project-scaffolder.ts b/packages/scaffold/create-sdk/src/project-scaffolder.ts similarity index 100% rename from packages/sdk/create-sdk/src/project-scaffolder.ts rename to packages/scaffold/create-sdk/src/project-scaffolder.ts diff --git a/packages/sdk/create-sdk/src/templates/assets/created.txt.tpl b/packages/scaffold/create-sdk/src/templates/assets/created.txt.tpl similarity index 100% rename from packages/sdk/create-sdk/src/templates/assets/created.txt.tpl rename to packages/scaffold/create-sdk/src/templates/assets/created.txt.tpl diff --git a/packages/sdk/create-sdk/src/templates/assets/install-question.txt.tpl b/packages/scaffold/create-sdk/src/templates/assets/install-question.txt.tpl similarity index 100% rename from packages/sdk/create-sdk/src/templates/assets/install-question.txt.tpl rename to packages/scaffold/create-sdk/src/templates/assets/install-question.txt.tpl diff --git a/packages/sdk/create-sdk/src/templates/assets/next-steps.txt.tpl b/packages/scaffold/create-sdk/src/templates/assets/next-steps.txt.tpl similarity index 100% rename from packages/sdk/create-sdk/src/templates/assets/next-steps.txt.tpl rename to packages/scaffold/create-sdk/src/templates/assets/next-steps.txt.tpl diff --git a/packages/sdk/create-sdk/src/templates/assets/setup-failure.txt.tpl b/packages/scaffold/create-sdk/src/templates/assets/setup-failure.txt.tpl similarity index 100% rename from packages/sdk/create-sdk/src/templates/assets/setup-failure.txt.tpl rename to packages/scaffold/create-sdk/src/templates/assets/setup-failure.txt.tpl diff --git a/packages/sdk/create-sdk/src/templates/assets/usage.txt.tpl b/packages/scaffold/create-sdk/src/templates/assets/usage.txt.tpl similarity index 100% rename from packages/sdk/create-sdk/src/templates/assets/usage.txt.tpl rename to packages/scaffold/create-sdk/src/templates/assets/usage.txt.tpl diff --git a/packages/sdk/create-sdk/src/templates/create-templates.ts b/packages/scaffold/create-sdk/src/templates/create-templates.ts similarity index 100% rename from packages/sdk/create-sdk/src/templates/create-templates.ts rename to packages/scaffold/create-sdk/src/templates/create-templates.ts diff --git a/packages/sdk/create-sdk/tests/built-artifacts.e2e.ts b/packages/scaffold/create-sdk/tests/built-artifacts.e2e.ts similarity index 87% rename from packages/sdk/create-sdk/tests/built-artifacts.e2e.ts rename to packages/scaffold/create-sdk/tests/built-artifacts.e2e.ts index 4b2de078c7..827abe06a2 100644 --- a/packages/sdk/create-sdk/tests/built-artifacts.e2e.ts +++ b/packages/scaffold/create-sdk/tests/built-artifacts.e2e.ts @@ -7,8 +7,8 @@ import { describe, expect, it } from 'vitest' const execFileAsync = promisify(execFile) const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)) -const createBin = join(repoRoot, 'packages/sdk/create-sdk/lib/bin.js') -const scriptsBin = join(repoRoot, 'packages/sdk/scripts/lib/bin.js') +const createBin = join(repoRoot, 'packages/scaffold/create-sdk/lib/bin.js') +const scriptsBin = join(repoRoot, 'packages/scaffold/scripts/lib/bin.js') describe.skipIf(!existsSync(createBin) || !existsSync(scriptsBin))( 'SDK built artifacts', diff --git a/packages/sdk/create-sdk/tests/create.snapshot.ts b/packages/scaffold/create-sdk/tests/create.snapshot.ts similarity index 100% rename from packages/sdk/create-sdk/tests/create.snapshot.ts rename to packages/scaffold/create-sdk/tests/create.snapshot.ts diff --git a/packages/sdk/create-sdk/tests/create.spec.ts b/packages/scaffold/create-sdk/tests/create.spec.ts similarity index 100% rename from packages/sdk/create-sdk/tests/create.spec.ts rename to packages/scaffold/create-sdk/tests/create.spec.ts diff --git a/packages/sdk/create-sdk/tests/link-workspace.e2e.ts b/packages/scaffold/create-sdk/tests/link-workspace.e2e.ts similarity index 98% rename from packages/sdk/create-sdk/tests/link-workspace.e2e.ts rename to packages/scaffold/create-sdk/tests/link-workspace.e2e.ts index b7739acc16..b978c8217e 100644 --- a/packages/sdk/create-sdk/tests/link-workspace.e2e.ts +++ b/packages/scaffold/create-sdk/tests/link-workspace.e2e.ts @@ -17,7 +17,7 @@ import { scaffoldProject } from '../src/project-scaffolder.ts' const execFileAsync = promisify(execFile) const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url)) -const builtScripts = join(repoRoot, 'packages/sdk/scripts/lib/bin.js') +const builtScripts = join(repoRoot, 'packages/scaffold/scripts/lib/bin.js') const temporary: string[] = [] function resolveCorepackHome(): string { diff --git a/packages/sdk/create-sdk/tsconfig.json b/packages/scaffold/create-sdk/tsconfig.json similarity index 100% rename from packages/sdk/create-sdk/tsconfig.json rename to packages/scaffold/create-sdk/tsconfig.json diff --git a/packages/sdk/create-sdk/tsdown.config.ts b/packages/scaffold/create-sdk/tsdown.config.ts similarity index 100% rename from packages/sdk/create-sdk/tsdown.config.ts rename to packages/scaffold/create-sdk/tsdown.config.ts diff --git a/packages/sdk/helper/README.i18n.yaml b/packages/scaffold/helper/README.i18n.yaml similarity index 80% rename from packages/sdk/helper/README.i18n.yaml rename to packages/scaffold/helper/README.i18n.yaml index bb5acbdcc2..a62db5f38a 100644 --- a/packages/sdk/helper/README.i18n.yaml +++ b/packages/scaffold/helper/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/helper/README.md +# pnpm run verify-translation-pairing --write packages/scaffold/helper/README.md README.md: 416fcab9815e50ca662333eb6925cc37eb0c41c4 README.zh.md: c50ec7aafa03377bd11759c50eeb2422a12fab68 diff --git a/packages/sdk/helper/README.md b/packages/scaffold/helper/README.md similarity index 100% rename from packages/sdk/helper/README.md rename to packages/scaffold/helper/README.md diff --git a/packages/sdk/helper/README.zh.md b/packages/scaffold/helper/README.zh.md similarity index 100% rename from packages/sdk/helper/README.zh.md rename to packages/scaffold/helper/README.zh.md diff --git a/packages/sdk/helper/package.json b/packages/scaffold/helper/package.json similarity index 100% rename from packages/sdk/helper/package.json rename to packages/scaffold/helper/package.json diff --git a/packages/sdk/helper/src/documents/cordis-yaml-file.ts b/packages/scaffold/helper/src/documents/cordis-yaml-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/cordis-yaml-file.ts rename to packages/scaffold/helper/src/documents/cordis-yaml-file.ts diff --git a/packages/sdk/helper/src/documents/env-file.ts b/packages/scaffold/helper/src/documents/env-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/env-file.ts rename to packages/scaffold/helper/src/documents/env-file.ts diff --git a/packages/sdk/helper/src/documents/package-json-file.ts b/packages/scaffold/helper/src/documents/package-json-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/package-json-file.ts rename to packages/scaffold/helper/src/documents/package-json-file.ts diff --git a/packages/sdk/helper/src/documents/pnpm-workspace-file.ts b/packages/scaffold/helper/src/documents/pnpm-workspace-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/pnpm-workspace-file.ts rename to packages/scaffold/helper/src/documents/pnpm-workspace-file.ts diff --git a/packages/sdk/helper/src/documents/project-file.ts b/packages/scaffold/helper/src/documents/project-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/project-file.ts rename to packages/scaffold/helper/src/documents/project-file.ts diff --git a/packages/sdk/helper/src/documents/tsconfig-file.ts b/packages/scaffold/helper/src/documents/tsconfig-file.ts similarity index 100% rename from packages/sdk/helper/src/documents/tsconfig-file.ts rename to packages/scaffold/helper/src/documents/tsconfig-file.ts diff --git a/packages/sdk/helper/src/features/builtin/app.ts b/packages/scaffold/helper/src/features/builtin/app.ts similarity index 100% rename from packages/sdk/helper/src/features/builtin/app.ts rename to packages/scaffold/helper/src/features/builtin/app.ts diff --git a/packages/sdk/helper/src/features/builtin/helpers.ts b/packages/scaffold/helper/src/features/builtin/helpers.ts similarity index 100% rename from packages/sdk/helper/src/features/builtin/helpers.ts rename to packages/scaffold/helper/src/features/builtin/helpers.ts diff --git a/packages/sdk/helper/src/features/builtin/index.ts b/packages/scaffold/helper/src/features/builtin/index.ts similarity index 100% rename from packages/sdk/helper/src/features/builtin/index.ts rename to packages/scaffold/helper/src/features/builtin/index.ts diff --git a/packages/sdk/helper/src/features/builtin/provider.ts b/packages/scaffold/helper/src/features/builtin/provider.ts similarity index 100% rename from packages/sdk/helper/src/features/builtin/provider.ts rename to packages/scaffold/helper/src/features/builtin/provider.ts diff --git a/packages/sdk/helper/src/features/builtin/spine.ts b/packages/scaffold/helper/src/features/builtin/spine.ts similarity index 100% rename from packages/sdk/helper/src/features/builtin/spine.ts rename to packages/scaffold/helper/src/features/builtin/spine.ts diff --git a/packages/sdk/helper/src/features/define-feature.ts b/packages/scaffold/helper/src/features/define-feature.ts similarity index 100% rename from packages/sdk/helper/src/features/define-feature.ts rename to packages/scaffold/helper/src/features/define-feature.ts diff --git a/packages/sdk/helper/src/features/feature-configurator.ts b/packages/scaffold/helper/src/features/feature-configurator.ts similarity index 100% rename from packages/sdk/helper/src/features/feature-configurator.ts rename to packages/scaffold/helper/src/features/feature-configurator.ts diff --git a/packages/sdk/helper/src/features/feature.ts b/packages/scaffold/helper/src/features/feature.ts similarity index 100% rename from packages/sdk/helper/src/features/feature.ts rename to packages/scaffold/helper/src/features/feature.ts diff --git a/packages/sdk/helper/src/features/registry.ts b/packages/scaffold/helper/src/features/registry.ts similarity index 100% rename from packages/sdk/helper/src/features/registry.ts rename to packages/scaffold/helper/src/features/registry.ts diff --git a/packages/sdk/helper/src/features/resources.ts b/packages/scaffold/helper/src/features/resources.ts similarity index 100% rename from packages/sdk/helper/src/features/resources.ts rename to packages/scaffold/helper/src/features/resources.ts diff --git a/packages/sdk/helper/src/ids.ts b/packages/scaffold/helper/src/ids.ts similarity index 100% rename from packages/sdk/helper/src/ids.ts rename to packages/scaffold/helper/src/ids.ts diff --git a/packages/sdk/helper/src/index.ts b/packages/scaffold/helper/src/index.ts similarity index 86% rename from packages/sdk/helper/src/index.ts rename to packages/scaffold/helper/src/index.ts index 85aba58a99..8c8e0db4b9 100644 --- a/packages/sdk/helper/src/index.ts +++ b/packages/scaffold/helper/src/index.ts @@ -1,6 +1,10 @@ /** * Shared domain and infrastructure for DeepSeek Harness SDK project tooling. * + * FIXME: rename to `@deepseek-ai/dsh-sdk-helper` before the first tagged release — + * the current name is indefensibly generic as a published name + * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * * @module @deepseek-ai/dsh-helper */ diff --git a/packages/sdk/helper/src/invariant.ts b/packages/scaffold/helper/src/invariant.ts similarity index 100% rename from packages/sdk/helper/src/invariant.ts rename to packages/scaffold/helper/src/invariant.ts diff --git a/packages/sdk/helper/src/package-managers/link-workspace.ts b/packages/scaffold/helper/src/package-managers/link-workspace.ts similarity index 100% rename from packages/sdk/helper/src/package-managers/link-workspace.ts rename to packages/scaffold/helper/src/package-managers/link-workspace.ts diff --git a/packages/sdk/helper/src/package-managers/package-manager.ts b/packages/scaffold/helper/src/package-managers/package-manager.ts similarity index 100% rename from packages/sdk/helper/src/package-managers/package-manager.ts rename to packages/scaffold/helper/src/package-managers/package-manager.ts diff --git a/packages/sdk/helper/src/plugins/local-plugin-blueprint.ts b/packages/scaffold/helper/src/plugins/local-plugin-blueprint.ts similarity index 100% rename from packages/sdk/helper/src/plugins/local-plugin-blueprint.ts rename to packages/scaffold/helper/src/plugins/local-plugin-blueprint.ts diff --git a/packages/sdk/helper/src/project/change-set.ts b/packages/scaffold/helper/src/project/change-set.ts similarity index 100% rename from packages/sdk/helper/src/project/change-set.ts rename to packages/scaffold/helper/src/project/change-set.ts diff --git a/packages/sdk/helper/src/project/npm-dependency-policy.ts b/packages/scaffold/helper/src/project/npm-dependency-policy.ts similarity index 100% rename from packages/sdk/helper/src/project/npm-dependency-policy.ts rename to packages/scaffold/helper/src/project/npm-dependency-policy.ts diff --git a/packages/sdk/helper/src/project/project-edit-session.ts b/packages/scaffold/helper/src/project/project-edit-session.ts similarity index 100% rename from packages/sdk/helper/src/project/project-edit-session.ts rename to packages/scaffold/helper/src/project/project-edit-session.ts diff --git a/packages/sdk/helper/src/project/sdk-project.ts b/packages/scaffold/helper/src/project/sdk-project.ts similarity index 99% rename from packages/sdk/helper/src/project/sdk-project.ts rename to packages/scaffold/helper/src/project/sdk-project.ts index e9302aa428..41b66fe7d0 100644 --- a/packages/sdk/helper/src/project/sdk-project.ts +++ b/packages/scaffold/helper/src/project/sdk-project.ts @@ -98,7 +98,7 @@ async function detectPackageManager(root: string, manifest: Readonly): string | undefined { const spec = manifest.dependencies?.['@deepseek-ai/dsh-scripts'] - const match = /^(?:file|link|portal):(.+)\/packages\/sdk\/scripts\/?$/.exec(spec ?? '') + const match = /^(?:file|link|portal):(.+)\/packages\/scaffold\/scripts\/?$/.exec(spec ?? '') return match?.[1] ? resolve(root, match[1]) : undefined } diff --git a/packages/sdk/helper/src/project/types.ts b/packages/scaffold/helper/src/project/types.ts similarity index 100% rename from packages/sdk/helper/src/project/types.ts rename to packages/scaffold/helper/src/project/types.ts diff --git a/packages/sdk/helper/src/questions/clack-nested-multiselect.ts b/packages/scaffold/helper/src/questions/clack-nested-multiselect.ts similarity index 100% rename from packages/sdk/helper/src/questions/clack-nested-multiselect.ts rename to packages/scaffold/helper/src/questions/clack-nested-multiselect.ts diff --git a/packages/sdk/helper/src/questions/clack-prompt-port.ts b/packages/scaffold/helper/src/questions/clack-prompt-port.ts similarity index 100% rename from packages/sdk/helper/src/questions/clack-prompt-port.ts rename to packages/scaffold/helper/src/questions/clack-prompt-port.ts diff --git a/packages/sdk/helper/src/questions/headless-prompt-port.ts b/packages/scaffold/helper/src/questions/headless-prompt-port.ts similarity index 100% rename from packages/sdk/helper/src/questions/headless-prompt-port.ts rename to packages/scaffold/helper/src/questions/headless-prompt-port.ts diff --git a/packages/sdk/helper/src/questions/prompt-port.ts b/packages/scaffold/helper/src/questions/prompt-port.ts similarity index 100% rename from packages/sdk/helper/src/questions/prompt-port.ts rename to packages/scaffold/helper/src/questions/prompt-port.ts diff --git a/packages/sdk/helper/src/questions/question.ts b/packages/scaffold/helper/src/questions/question.ts similarity index 100% rename from packages/sdk/helper/src/questions/question.ts rename to packages/scaffold/helper/src/questions/question.ts diff --git a/packages/sdk/helper/src/templates/assets/README.md.tpl b/packages/scaffold/helper/src/templates/assets/README.md.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/README.md.tpl rename to packages/scaffold/helper/src/templates/assets/README.md.tpl diff --git a/packages/sdk/helper/src/templates/assets/gitignore.tpl b/packages/scaffold/helper/src/templates/assets/gitignore.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/gitignore.tpl rename to packages/scaffold/helper/src/templates/assets/gitignore.tpl diff --git a/packages/sdk/helper/src/templates/assets/index.ts.tpl b/packages/scaffold/helper/src/templates/assets/index.ts.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/index.ts.tpl rename to packages/scaffold/helper/src/templates/assets/index.ts.tpl diff --git a/packages/sdk/helper/src/templates/assets/local-plugin-tsdown.config.ts.tpl b/packages/scaffold/helper/src/templates/assets/local-plugin-tsdown.config.ts.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/local-plugin-tsdown.config.ts.tpl rename to packages/scaffold/helper/src/templates/assets/local-plugin-tsdown.config.ts.tpl diff --git a/packages/sdk/helper/src/templates/assets/local-plugin.ts.tpl b/packages/scaffold/helper/src/templates/assets/local-plugin.ts.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/local-plugin.ts.tpl rename to packages/scaffold/helper/src/templates/assets/local-plugin.ts.tpl diff --git a/packages/sdk/helper/src/templates/assets/local-tool.ts.tpl b/packages/scaffold/helper/src/templates/assets/local-tool.ts.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/local-tool.ts.tpl rename to packages/scaffold/helper/src/templates/assets/local-tool.ts.tpl diff --git a/packages/sdk/helper/src/templates/assets/package.json.tpl b/packages/scaffold/helper/src/templates/assets/package.json.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/package.json.tpl rename to packages/scaffold/helper/src/templates/assets/package.json.tpl diff --git a/packages/sdk/helper/src/templates/assets/persona.txt.tpl b/packages/scaffold/helper/src/templates/assets/persona.txt.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/persona.txt.tpl rename to packages/scaffold/helper/src/templates/assets/persona.txt.tpl diff --git a/packages/sdk/helper/src/templates/assets/tsconfig.base.json.tpl b/packages/scaffold/helper/src/templates/assets/tsconfig.base.json.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/tsconfig.base.json.tpl rename to packages/scaffold/helper/src/templates/assets/tsconfig.base.json.tpl diff --git a/packages/sdk/helper/src/templates/assets/tsdown.config.ts.tpl b/packages/scaffold/helper/src/templates/assets/tsdown.config.ts.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/tsdown.config.ts.tpl rename to packages/scaffold/helper/src/templates/assets/tsdown.config.ts.tpl diff --git a/packages/sdk/helper/src/templates/assets/yarnrc.yml.tpl b/packages/scaffold/helper/src/templates/assets/yarnrc.yml.tpl similarity index 100% rename from packages/sdk/helper/src/templates/assets/yarnrc.yml.tpl rename to packages/scaffold/helper/src/templates/assets/yarnrc.yml.tpl diff --git a/packages/sdk/helper/src/templates/project-template.ts b/packages/scaffold/helper/src/templates/project-template.ts similarity index 100% rename from packages/sdk/helper/src/templates/project-template.ts rename to packages/scaffold/helper/src/templates/project-template.ts diff --git a/packages/sdk/helper/src/templates/template-assets.ts b/packages/scaffold/helper/src/templates/template-assets.ts similarity index 100% rename from packages/sdk/helper/src/templates/template-assets.ts rename to packages/scaffold/helper/src/templates/template-assets.ts diff --git a/packages/sdk/helper/src/templates/text-template.ts b/packages/scaffold/helper/src/templates/text-template.ts similarity index 100% rename from packages/sdk/helper/src/templates/text-template.ts rename to packages/scaffold/helper/src/templates/text-template.ts diff --git a/packages/sdk/helper/tests/documents.spec.ts b/packages/scaffold/helper/tests/documents.spec.ts similarity index 100% rename from packages/sdk/helper/tests/documents.spec.ts rename to packages/scaffold/helper/tests/documents.spec.ts diff --git a/packages/sdk/helper/tests/headless-prompt-port.spec.ts b/packages/scaffold/helper/tests/headless-prompt-port.spec.ts similarity index 100% rename from packages/sdk/helper/tests/headless-prompt-port.spec.ts rename to packages/scaffold/helper/tests/headless-prompt-port.spec.ts diff --git a/packages/sdk/helper/tests/project.spec.ts b/packages/scaffold/helper/tests/project.spec.ts similarity index 100% rename from packages/sdk/helper/tests/project.spec.ts rename to packages/scaffold/helper/tests/project.spec.ts diff --git a/packages/sdk/helper/tests/questions.spec.ts b/packages/scaffold/helper/tests/questions.spec.ts similarity index 100% rename from packages/sdk/helper/tests/questions.spec.ts rename to packages/scaffold/helper/tests/questions.spec.ts diff --git a/packages/sdk/helper/tsconfig.json b/packages/scaffold/helper/tsconfig.json similarity index 100% rename from packages/sdk/helper/tsconfig.json rename to packages/scaffold/helper/tsconfig.json diff --git a/packages/sdk/helper/tsdown.config.ts b/packages/scaffold/helper/tsdown.config.ts similarity index 100% rename from packages/sdk/helper/tsdown.config.ts rename to packages/scaffold/helper/tsdown.config.ts diff --git a/packages/scaffold/protocol/README.i18n.yaml b/packages/scaffold/protocol/README.i18n.yaml new file mode 100644 index 0000000000..df0623573b --- /dev/null +++ b/packages/scaffold/protocol/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/scaffold/protocol/README.md +README.md: dc42f385f6e7fd6327ca79887d3cf22d7c34ccdf +README.zh.md: a292120be8b5b09aaadb05f9dbc272a51bd74831 diff --git a/packages/sdk/sdk-protocol/README.md b/packages/scaffold/protocol/README.md similarity index 90% rename from packages/sdk/sdk-protocol/README.md rename to packages/scaffold/protocol/README.md index 2d141e6f62..b8420eb19a 100644 --- a/packages/sdk/sdk-protocol/README.md +++ b/packages/scaffold/protocol/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delimited JSON-RPC 2.0 transport class plus the named request, result, and notification types both wire ends speak. The package root enumerates the protocol consumer interface; source modules are not exported as deep imports. The server side is the [`dsh-jsonrpc`](../../ui/jsonrpc/README.md) plugin; clients are [`dsh-sdk-client`](../sdk-client/README.md) (TypeScript) and the [Python SDK](../../../python/README.md) (which mirrors these shapes but does not import them). A pure library — no plugin, no Config, no registration. +The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delimited JSON-RPC 2.0 transport class plus the named request, result, and notification types both wire ends speak. The package root enumerates the protocol consumer interface; source modules are not exported as deep imports. The server side is the [`dsh-jsonrpc`](../../scaffold/server/README.md) plugin; clients are [`dsh-sdk-client`](../client/README.md) (TypeScript) and the [Python SDK](../../../python/README.md) (which mirrors these shapes but does not import them). A pure library — no plugin, no Config, no registration. ## Transport @@ -26,7 +26,7 @@ The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delim ## Model Experience -None, as this package defines the client-facing wire protocol; the model-visible surfaces belong to the runtime plugins composed behind the serving [`dsh-jsonrpc`](../../ui/jsonrpc/README.md) entry. +None, as this package defines the client-facing wire protocol; the model-visible surfaces belong to the runtime plugins composed behind the serving [`dsh-jsonrpc`](../../scaffold/server/README.md) entry. #### KV Cache effect @@ -35,5 +35,5 @@ None; this package neither assembles nor sends a provider request. ## Known Limitations and Deferred Work - **No protocol-version negotiation** — the handshake carries only `serverInfo.version` (`0.0.1`, unvalidated by clients); pre-release stance, no compatibility promise. -- **No cancel or session-close methods** — a client abandons a turn by closing the runtime process; see the [`dsh-jsonrpc` README](../../ui/jsonrpc/README.md). +- **No cancel or session-close methods** — a client abandons a turn by closing the runtime process; see the [`dsh-jsonrpc` README](../../scaffold/server/README.md). - **Server→client requests are dead capability** — the transport supports them, but the server never sends one; the Python SDK's responder surface exists for future approval flows. diff --git a/packages/sdk/sdk-protocol/README.zh.md b/packages/scaffold/protocol/README.zh.md similarity index 100% rename from packages/sdk/sdk-protocol/README.zh.md rename to packages/scaffold/protocol/README.zh.md diff --git a/packages/sdk/sdk-protocol/package.json b/packages/scaffold/protocol/package.json similarity index 100% rename from packages/sdk/sdk-protocol/package.json rename to packages/scaffold/protocol/package.json diff --git a/packages/sdk/sdk-protocol/src/index.ts b/packages/scaffold/protocol/src/index.ts similarity index 100% rename from packages/sdk/sdk-protocol/src/index.ts rename to packages/scaffold/protocol/src/index.ts diff --git a/packages/sdk/sdk-protocol/src/invariant.ts b/packages/scaffold/protocol/src/invariant.ts similarity index 100% rename from packages/sdk/sdk-protocol/src/invariant.ts rename to packages/scaffold/protocol/src/invariant.ts diff --git a/packages/sdk/sdk-protocol/src/transport.ts b/packages/scaffold/protocol/src/transport.ts similarity index 100% rename from packages/sdk/sdk-protocol/src/transport.ts rename to packages/scaffold/protocol/src/transport.ts diff --git a/packages/sdk/sdk-protocol/src/types.ts b/packages/scaffold/protocol/src/types.ts similarity index 100% rename from packages/sdk/sdk-protocol/src/types.ts rename to packages/scaffold/protocol/src/types.ts diff --git a/packages/sdk/sdk-protocol/tests/transport.spec.ts b/packages/scaffold/protocol/tests/transport.spec.ts similarity index 100% rename from packages/sdk/sdk-protocol/tests/transport.spec.ts rename to packages/scaffold/protocol/tests/transport.spec.ts diff --git a/packages/sdk/sdk-protocol/tsconfig.json b/packages/scaffold/protocol/tsconfig.json similarity index 100% rename from packages/sdk/sdk-protocol/tsconfig.json rename to packages/scaffold/protocol/tsconfig.json diff --git a/packages/sdk/scripts/README.i18n.yaml b/packages/scaffold/scripts/README.i18n.yaml similarity index 80% rename from packages/sdk/scripts/README.i18n.yaml rename to packages/scaffold/scripts/README.i18n.yaml index b148eb76ab..db65cb315f 100644 --- a/packages/sdk/scripts/README.i18n.yaml +++ b/packages/scaffold/scripts/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/scripts/README.md +# pnpm run verify-translation-pairing --write packages/scaffold/scripts/README.md README.md: 9a696bf5a4de9a80f0741f07a7e753733bc2f998 README.zh.md: cf9fc2cbbe467eb86e89d9eb77e9bc34860b5a3a diff --git a/packages/sdk/scripts/README.md b/packages/scaffold/scripts/README.md similarity index 100% rename from packages/sdk/scripts/README.md rename to packages/scaffold/scripts/README.md diff --git a/packages/sdk/scripts/README.zh.md b/packages/scaffold/scripts/README.zh.md similarity index 100% rename from packages/sdk/scripts/README.zh.md rename to packages/scaffold/scripts/README.zh.md diff --git a/packages/sdk/scripts/package.json b/packages/scaffold/scripts/package.json similarity index 100% rename from packages/sdk/scripts/package.json rename to packages/scaffold/scripts/package.json diff --git a/packages/sdk/scripts/src/args.ts b/packages/scaffold/scripts/src/args.ts similarity index 100% rename from packages/sdk/scripts/src/args.ts rename to packages/scaffold/scripts/src/args.ts diff --git a/packages/sdk/scripts/src/bin.ts b/packages/scaffold/scripts/src/bin.ts similarity index 100% rename from packages/sdk/scripts/src/bin.ts rename to packages/scaffold/scripts/src/bin.ts diff --git a/packages/sdk/scripts/src/build.ts b/packages/scaffold/scripts/src/build.ts similarity index 100% rename from packages/sdk/scripts/src/build.ts rename to packages/scaffold/scripts/src/build.ts diff --git a/packages/sdk/scripts/src/command.ts b/packages/scaffold/scripts/src/command.ts similarity index 100% rename from packages/sdk/scripts/src/command.ts rename to packages/scaffold/scripts/src/command.ts diff --git a/packages/sdk/scripts/src/config.ts b/packages/scaffold/scripts/src/config.ts similarity index 100% rename from packages/sdk/scripts/src/config.ts rename to packages/scaffold/scripts/src/config.ts diff --git a/packages/sdk/scripts/src/config/config-workflow.ts b/packages/scaffold/scripts/src/config/config-workflow.ts similarity index 100% rename from packages/sdk/scripts/src/config/config-workflow.ts rename to packages/scaffold/scripts/src/config/config-workflow.ts diff --git a/packages/sdk/scripts/src/create-plugin.ts b/packages/scaffold/scripts/src/create-plugin.ts similarity index 100% rename from packages/sdk/scripts/src/create-plugin.ts rename to packages/scaffold/scripts/src/create-plugin.ts diff --git a/packages/sdk/scripts/src/dev/tsdown-config.ts b/packages/scaffold/scripts/src/dev/tsdown-config.ts similarity index 100% rename from packages/sdk/scripts/src/dev/tsdown-config.ts rename to packages/scaffold/scripts/src/dev/tsdown-config.ts diff --git a/packages/scaffold/scripts/src/index.ts b/packages/scaffold/scripts/src/index.ts new file mode 100644 index 0000000000..0af3ef0b30 --- /dev/null +++ b/packages/scaffold/scripts/src/index.ts @@ -0,0 +1,11 @@ +/** + * Public DeepSeek Harness SDK runtime entry points. + * + * FIXME: rename to `@deepseek-ai/dsh-sdk-scripts` before the first tagged release — + * the current name is indefensibly generic as a published name + * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * + * @module @deepseek-ai/dsh-scripts + */ + +export { runSDK, startSDK, type SdkBootContext } from './runtime.ts' diff --git a/packages/sdk/scripts/src/invariant.ts b/packages/scaffold/scripts/src/invariant.ts similarity index 100% rename from packages/sdk/scripts/src/invariant.ts rename to packages/scaffold/scripts/src/invariant.ts diff --git a/packages/sdk/scripts/src/local-plugin-loader-hooks.ts b/packages/scaffold/scripts/src/local-plugin-loader-hooks.ts similarity index 100% rename from packages/sdk/scripts/src/local-plugin-loader-hooks.ts rename to packages/scaffold/scripts/src/local-plugin-loader-hooks.ts diff --git a/packages/sdk/scripts/src/runtime.ts b/packages/scaffold/scripts/src/runtime.ts similarity index 100% rename from packages/sdk/scripts/src/runtime.ts rename to packages/scaffold/scripts/src/runtime.ts diff --git a/packages/sdk/scripts/src/telemetry.ts b/packages/scaffold/scripts/src/telemetry.ts similarity index 100% rename from packages/sdk/scripts/src/telemetry.ts rename to packages/scaffold/scripts/src/telemetry.ts diff --git a/packages/sdk/scripts/src/templates/assets/config-install-failure.txt.tpl b/packages/scaffold/scripts/src/templates/assets/config-install-failure.txt.tpl similarity index 100% rename from packages/sdk/scripts/src/templates/assets/config-install-failure.txt.tpl rename to packages/scaffold/scripts/src/templates/assets/config-install-failure.txt.tpl diff --git a/packages/sdk/scripts/src/templates/assets/usage.txt.tpl b/packages/scaffold/scripts/src/templates/assets/usage.txt.tpl similarity index 100% rename from packages/sdk/scripts/src/templates/assets/usage.txt.tpl rename to packages/scaffold/scripts/src/templates/assets/usage.txt.tpl diff --git a/packages/sdk/scripts/src/templates/dsh-sdk-templates.ts b/packages/scaffold/scripts/src/templates/dsh-sdk-templates.ts similarity index 100% rename from packages/sdk/scripts/src/templates/dsh-sdk-templates.ts rename to packages/scaffold/scripts/src/templates/dsh-sdk-templates.ts diff --git a/packages/sdk/scripts/tests/__snapshots__/config.snapshot.ts.snap b/packages/scaffold/scripts/tests/__snapshots__/config.snapshot.ts.snap similarity index 100% rename from packages/sdk/scripts/tests/__snapshots__/config.snapshot.ts.snap rename to packages/scaffold/scripts/tests/__snapshots__/config.snapshot.ts.snap diff --git a/packages/sdk/scripts/tests/config.snapshot.ts b/packages/scaffold/scripts/tests/config.snapshot.ts similarity index 100% rename from packages/sdk/scripts/tests/config.snapshot.ts rename to packages/scaffold/scripts/tests/config.snapshot.ts diff --git a/packages/sdk/scripts/tests/scripts.spec.ts b/packages/scaffold/scripts/tests/scripts.spec.ts similarity index 100% rename from packages/sdk/scripts/tests/scripts.spec.ts rename to packages/scaffold/scripts/tests/scripts.spec.ts diff --git a/packages/sdk/scripts/tsconfig.json b/packages/scaffold/scripts/tsconfig.json similarity index 88% rename from packages/sdk/scripts/tsconfig.json rename to packages/scaffold/scripts/tsconfig.json index d3dacea10b..2230a377ab 100644 --- a/packages/sdk/scripts/tsconfig.json +++ b/packages/scaffold/scripts/tsconfig.json @@ -8,7 +8,7 @@ "references": [ { "path": "../helper" }, { "path": "../telemetry" }, - { "path": "../../ui/app-boot" }, + { "path": "../../boot/app-boot" }, { "path": "../../../vendor/cordis" }, { "path": "../../support/invariants" } ] diff --git a/packages/sdk/scripts/tsdown.config.ts b/packages/scaffold/scripts/tsdown.config.ts similarity index 100% rename from packages/sdk/scripts/tsdown.config.ts rename to packages/scaffold/scripts/tsdown.config.ts diff --git a/packages/scaffold/server/README.i18n.yaml b/packages/scaffold/server/README.i18n.yaml new file mode 100644 index 0000000000..0672ad22a6 --- /dev/null +++ b/packages/scaffold/server/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/scaffold/server/README.md +README.md: 7ada4fbe6a53e90fcd45c20d9472cb87dbf6b4cf +README.zh.md: b8362f7762b963ed6caff1b2ba87421152ecb483 diff --git a/packages/ui/jsonrpc/README.md b/packages/scaffold/server/README.md similarity index 95% rename from packages/ui/jsonrpc/README.md rename to packages/scaffold/server/README.md index 9cd4876b52..0afb3a38ec 100644 --- a/packages/ui/jsonrpc/README.md +++ b/packages/scaffold/server/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The `jsonrpc` plugin serves newline-delimited JSON-RPC over stdio so out-of-process SDK clients can drive harness agents. [`HarnessSdkServer`](src/server.ts) owns the protocol methods and notifications; the transport and the named wire types live in [`dsh-sdk-protocol`](../../sdk/sdk-protocol/README.md), shared with the client SDKs; [`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) supplies the surrounding `cordis.yml` application. +The `jsonrpc` plugin serves newline-delimited JSON-RPC over stdio so out-of-process SDK clients can drive harness agents. [`HarnessSdkServer`](src/server.ts) owns the protocol methods and notifications; the transport and the named wire types live in [`dsh-sdk-protocol`](../../scaffold/protocol/README.md), shared with the client SDKs; [`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) supplies the surrounding `cordis.yml` application. ## Wiring diff --git a/packages/ui/jsonrpc/README.zh.md b/packages/scaffold/server/README.zh.md similarity index 95% rename from packages/ui/jsonrpc/README.zh.md rename to packages/scaffold/server/README.zh.md index a2b979da44..5305434abb 100644 --- a/packages/ui/jsonrpc/README.zh.md +++ b/packages/scaffold/server/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -`jsonrpc` 插件通过 stdio 提供以换行符分隔的 JSON-RPC,使进程外 SDK 客户端能够驱动 harness agent(智能体)。[`HarnessSdkServer`](src/server.ts) 负责协议方法和通知;传输与具名协议类型位于 [`dsh-sdk-protocol`](../../sdk/sdk-protocol/README.md),与客户端 SDK 共享;[`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) 提供外围的 `cordis.yml` 应用。 +`jsonrpc` 插件通过 stdio 提供以换行符分隔的 JSON-RPC,使进程外 SDK 客户端能够驱动 harness agent(智能体)。[`HarnessSdkServer`](src/server.ts) 负责协议方法和通知;传输与具名协议类型位于 [`dsh-sdk-protocol`](../../scaffold/protocol/README.md),与客户端 SDK 共享;[`jsonrpc-demo`](../../examples/jsonrpc-demo/README.md) 提供外围的 `cordis.yml` 应用。 ## 组装 diff --git a/packages/ui/jsonrpc/package.json b/packages/scaffold/server/package.json similarity index 100% rename from packages/ui/jsonrpc/package.json rename to packages/scaffold/server/package.json diff --git a/packages/ui/jsonrpc/src/index.ts b/packages/scaffold/server/src/index.ts similarity index 92% rename from packages/ui/jsonrpc/src/index.ts rename to packages/scaffold/server/src/index.ts index 423f338877..9985cadfb4 100644 --- a/packages/ui/jsonrpc/src/index.ts +++ b/packages/scaffold/server/src/index.ts @@ -6,6 +6,10 @@ * owns EOF and signal exits. Keep named plugin exports with no default export so * Loader `unwrapExports` preserves `name`, `inject`, `Config`, and `apply`. * + * FIXME: rename to `@deepseek-ai/dsh-sdk-server` before the first tagged release — + * the current name says the wire encoding, not the role; it is the server half of + * the SDK protocol ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * * @module @deepseek-ai/dsh-jsonrpc */ diff --git a/packages/ui/jsonrpc/src/invariant.ts b/packages/scaffold/server/src/invariant.ts similarity index 100% rename from packages/ui/jsonrpc/src/invariant.ts rename to packages/scaffold/server/src/invariant.ts diff --git a/packages/ui/jsonrpc/src/server.ts b/packages/scaffold/server/src/server.ts similarity index 100% rename from packages/ui/jsonrpc/src/server.ts rename to packages/scaffold/server/src/server.ts diff --git a/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts b/packages/scaffold/server/tests/built-scope-carrier.e2e.ts similarity index 98% rename from packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts rename to packages/scaffold/server/tests/built-scope-carrier.e2e.ts index 1de16660a8..b3519110b5 100644 --- a/packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts +++ b/packages/scaffold/server/tests/built-scope-carrier.e2e.ts @@ -36,7 +36,7 @@ const [ load("packages/examples/agent-spine-demo/lib/index.js"), load("packages/subagent/subagent/lib/index.js"), load("packages/session/session-persistence-jsonl/lib/index.js"), - load("packages/ui/jsonrpc/lib/index.js"), + load("packages/scaffold/server/lib/index.js"), load("packages/core/session/lib/index.js"), ]); diff --git a/packages/ui/jsonrpc/tests/plugin-apply.spec.ts b/packages/scaffold/server/tests/plugin-apply.spec.ts similarity index 100% rename from packages/ui/jsonrpc/tests/plugin-apply.spec.ts rename to packages/scaffold/server/tests/plugin-apply.spec.ts diff --git a/packages/ui/jsonrpc/tests/plugin-shape.spec.ts b/packages/scaffold/server/tests/plugin-shape.spec.ts similarity index 100% rename from packages/ui/jsonrpc/tests/plugin-shape.spec.ts rename to packages/scaffold/server/tests/plugin-shape.spec.ts diff --git a/packages/ui/jsonrpc/tests/server.spec.ts b/packages/scaffold/server/tests/server.spec.ts similarity index 100% rename from packages/ui/jsonrpc/tests/server.spec.ts rename to packages/scaffold/server/tests/server.spec.ts diff --git a/packages/ui/jsonrpc/tsconfig.json b/packages/scaffold/server/tsconfig.json similarity index 93% rename from packages/ui/jsonrpc/tsconfig.json rename to packages/scaffold/server/tsconfig.json index 00ac7000e6..56419b6b84 100644 --- a/packages/ui/jsonrpc/tsconfig.json +++ b/packages/scaffold/server/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../core/session" }, { - "path": "../../sdk/sdk-protocol" + "path": "../protocol" }, { "path": "../../subagent/subagent" diff --git a/packages/sdk/telemetry/README.i18n.yaml b/packages/scaffold/telemetry/README.i18n.yaml similarity index 80% rename from packages/sdk/telemetry/README.i18n.yaml rename to packages/scaffold/telemetry/README.i18n.yaml index 1604b40bf4..4d70fce157 100644 --- a/packages/sdk/telemetry/README.i18n.yaml +++ b/packages/scaffold/telemetry/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/telemetry/README.md +# pnpm run verify-translation-pairing --write packages/scaffold/telemetry/README.md README.md: c87735a93e7659f2913f4dd325176a8ae40cf29b README.zh.md: 24d6e72d988f94cdbc6aa01607edbfc105213267 diff --git a/packages/sdk/telemetry/README.md b/packages/scaffold/telemetry/README.md similarity index 100% rename from packages/sdk/telemetry/README.md rename to packages/scaffold/telemetry/README.md diff --git a/packages/sdk/telemetry/README.zh.md b/packages/scaffold/telemetry/README.zh.md similarity index 100% rename from packages/sdk/telemetry/README.zh.md rename to packages/scaffold/telemetry/README.zh.md diff --git a/packages/sdk/telemetry/package.json b/packages/scaffold/telemetry/package.json similarity index 100% rename from packages/sdk/telemetry/package.json rename to packages/scaffold/telemetry/package.json diff --git a/packages/sdk/telemetry/src/anonymous-id.ts b/packages/scaffold/telemetry/src/anonymous-id.ts similarity index 100% rename from packages/sdk/telemetry/src/anonymous-id.ts rename to packages/scaffold/telemetry/src/anonymous-id.ts diff --git a/packages/sdk/telemetry/src/consent-resolver.ts b/packages/scaffold/telemetry/src/consent-resolver.ts similarity index 100% rename from packages/sdk/telemetry/src/consent-resolver.ts rename to packages/scaffold/telemetry/src/consent-resolver.ts diff --git a/packages/sdk/telemetry/src/index.ts b/packages/scaffold/telemetry/src/index.ts similarity index 82% rename from packages/sdk/telemetry/src/index.ts rename to packages/scaffold/telemetry/src/index.ts index 107956fa39..17ea1d02c7 100644 --- a/packages/sdk/telemetry/src/index.ts +++ b/packages/scaffold/telemetry/src/index.ts @@ -7,6 +7,10 @@ * the launcher command dispatch and the helper feature catalog lives outside * this package. * + * FIXME: rename to `@deepseek-ai/dsh-sdk-telemetry` before the first tagged release — + * the current name collides with the `dsh-session-telemetry` family; this is + * launcher-side SDK telemetry ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * * @module @deepseek-ai/dsh-telemetry */ diff --git a/packages/sdk/telemetry/src/invariant.ts b/packages/scaffold/telemetry/src/invariant.ts similarity index 100% rename from packages/sdk/telemetry/src/invariant.ts rename to packages/scaffold/telemetry/src/invariant.ts diff --git a/packages/sdk/telemetry/src/payload.ts b/packages/scaffold/telemetry/src/payload.ts similarity index 100% rename from packages/sdk/telemetry/src/payload.ts rename to packages/scaffold/telemetry/src/payload.ts diff --git a/packages/sdk/telemetry/src/reporter.ts b/packages/scaffold/telemetry/src/reporter.ts similarity index 100% rename from packages/sdk/telemetry/src/reporter.ts rename to packages/scaffold/telemetry/src/reporter.ts diff --git a/packages/sdk/telemetry/src/secret-redactor.ts b/packages/scaffold/telemetry/src/secret-redactor.ts similarity index 100% rename from packages/sdk/telemetry/src/secret-redactor.ts rename to packages/scaffold/telemetry/src/secret-redactor.ts diff --git a/packages/sdk/telemetry/tests/anonymous-id.spec.ts b/packages/scaffold/telemetry/tests/anonymous-id.spec.ts similarity index 100% rename from packages/sdk/telemetry/tests/anonymous-id.spec.ts rename to packages/scaffold/telemetry/tests/anonymous-id.spec.ts diff --git a/packages/sdk/telemetry/tests/consent-resolver.spec.ts b/packages/scaffold/telemetry/tests/consent-resolver.spec.ts similarity index 100% rename from packages/sdk/telemetry/tests/consent-resolver.spec.ts rename to packages/scaffold/telemetry/tests/consent-resolver.spec.ts diff --git a/packages/sdk/telemetry/tests/payload.spec.ts b/packages/scaffold/telemetry/tests/payload.spec.ts similarity index 100% rename from packages/sdk/telemetry/tests/payload.spec.ts rename to packages/scaffold/telemetry/tests/payload.spec.ts diff --git a/packages/sdk/telemetry/tests/reporter.spec.ts b/packages/scaffold/telemetry/tests/reporter.spec.ts similarity index 100% rename from packages/sdk/telemetry/tests/reporter.spec.ts rename to packages/scaffold/telemetry/tests/reporter.spec.ts diff --git a/packages/sdk/telemetry/tests/secret-redactor.spec.ts b/packages/scaffold/telemetry/tests/secret-redactor.spec.ts similarity index 100% rename from packages/sdk/telemetry/tests/secret-redactor.spec.ts rename to packages/scaffold/telemetry/tests/secret-redactor.spec.ts diff --git a/packages/sdk/telemetry/tsconfig.json b/packages/scaffold/telemetry/tsconfig.json similarity index 100% rename from packages/sdk/telemetry/tsconfig.json rename to packages/scaffold/telemetry/tsconfig.json diff --git a/packages/sdk/README.zh.md b/packages/sdk/README.zh.md deleted file mode 100644 index 003383c5f6..0000000000 --- a/packages/sdk/README.zh.md +++ /dev/null @@ -1,16 +0,0 @@ -# SDK 包 - -[English](README.md) | 中文 - -本分组包含 Harness 项目的开发者工具,以及从另一个进程驱动 Harness 运行时的客户端栈。 - -| 包 | 职责 | -|---|---| -| [`helper/`](helper/README.md) | 提供共享的项目编辑领域 | -| [`scripts/`](scripts/README.md) | 提供 `dsh-sdk` 项目命令 | -| [`create-sdk/`](create-sdk/README.md) | 创建新的 SDK 项目 | -| [`sdk-protocol/`](sdk-protocol/README.md) | 定义 SDK 运行时协议格式 | -| [`sdk-client/`](sdk-client/README.md) | 通过 TypeScript 客户端 API 驱动 Harness 运行时 | -| [`telemetry/`](telemetry/README.md) | 提供启动器遥测、同意和脱敏原语 | - -`@deepseek-ai/create-sdk` 遵循 npm 的 scoped initializer 命名约定;其他包遵循仓库的 `@deepseek-ai/dsh-*` 约定。参见[开发者项目工作流](../../.agents/notes/proposed/feature/2026-07-14-sdk-developer-projects.md)、[项目编辑架构](../../.agents/notes/proposed/architecture/2026-07-15-sdk-project-editing-architecture.md)和 [TypeScript SDK 设计](../../.agents/notes/implemented/feature/2026-07-27-typescript-sdk-and-sdk-subagent-backend.md)。 diff --git a/packages/sdk/scripts/src/index.ts b/packages/sdk/scripts/src/index.ts deleted file mode 100644 index 5180f885c8..0000000000 --- a/packages/sdk/scripts/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Public DeepSeek Harness SDK runtime entry points. - * - * @module @deepseek-ai/dsh-scripts - */ - -export { runSDK, startSDK, type SdkBootContext } from './runtime.ts' diff --git a/packages/sdk/sdk-protocol/README.i18n.yaml b/packages/sdk/sdk-protocol/README.i18n.yaml deleted file mode 100644 index f878b9fd63..0000000000 --- a/packages/sdk/sdk-protocol/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/sdk/sdk-protocol/README.md -README.md: 2d141e6f62ac0d324933996c282a2d0380af29f2 -README.zh.md: bc459f8817c9477a451015f7629168e246ee531c diff --git a/packages/subagent/subagent-dsh-sdk/README.i18n.yaml b/packages/subagent/subagent-dsh-sdk/README.i18n.yaml index 3598c77d31..246d702892 100644 --- a/packages/subagent/subagent-dsh-sdk/README.i18n.yaml +++ b/packages/subagent/subagent-dsh-sdk/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subagent/subagent-dsh-sdk/README.md -README.md: c0e2f4e9ece366e28492e32d97afa533fd948141 -README.zh.md: 4cf6c58fff44b1c11cf0a6c321da2c02d52bb8c9 +README.md: 0bbcfa105ecf024a2492d39d3bf8d28956110050 +README.zh.md: ffb1b8f4cddca4566a20593bdac54801ad8db38f diff --git a/packages/subagent/subagent-dsh-sdk/README.md b/packages/subagent/subagent-dsh-sdk/README.md index c0e2f4e9ec..0bbcfa105e 100644 --- a/packages/subagent/subagent-dsh-sdk/README.md +++ b/packages/subagent/subagent-dsh-sdk/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The SDK provider runs each subagent as a complete DeepSeek Harness runtime in a fresh subprocess, driven over stdio JSON-RPC through the [TypeScript SDK client](../../sdk/sdk-client/README.md). It is the second out-of-process backend beside [`subagent-acp`](../subagent-acp/README.md), differing in the wire and the child contract: the ACP backend drives any Agent Client Protocol agent; this backend drives specifically a harness SDK runtime (`dsh-jsonrpc-agent` bin or packaged executable), so the child is a full peer harness — own `cordis.yml`-decided composition, session persistence, model route, and tools. +The SDK provider runs each subagent as a complete DeepSeek Harness runtime in a fresh subprocess, driven over stdio JSON-RPC through the [TypeScript SDK client](../../scaffold/client/README.md). It is the second out-of-process backend beside [`subagent-acp`](../subagent-acp/README.md), differing in the wire and the child contract: the ACP backend drives any Agent Client Protocol agent; this backend drives specifically a harness SDK runtime (`dsh-jsonrpc-agent` bin or packaged executable), so the child is a full peer harness — own `cordis.yml`-decided composition, session persistence, model route, and tools. ## Start and ownership diff --git a/packages/subagent/subagent-dsh-sdk/README.zh.md b/packages/subagent/subagent-dsh-sdk/README.zh.md index 4cf6c58fff..ffb1b8f4cd 100644 --- a/packages/subagent/subagent-dsh-sdk/README.zh.md +++ b/packages/subagent/subagent-dsh-sdk/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -SDK 提供方会在全新的子进程中把每个 subagent 作为完整的 DeepSeek Harness 运行时运行,并经由 [TypeScript SDK 客户端](../../sdk/sdk-client/README.md) 通过 stdio JSON-RPC 驱动。它是 [`subagent-acp`](../subagent-acp/README.md) 之外的第二个进程外后端,差异在协议格式(wire format)和子进程契约:ACP(Agent Client Protocol)后端能驱动任何 Agent Client Protocol agent(智能体);本后端专门驱动 harness SDK 运行时(`dsh-jsonrpc-agent` bin 或打包后的可执行文件),因此子进程是一个完整的对等 harness,拥有由 `cordis.yml` 决定的组合、会话持久化、模型路由和工具。 +SDK 提供方会在全新的子进程中把每个 subagent 作为完整的 DeepSeek Harness 运行时运行,并经由 [TypeScript SDK 客户端](../../scaffold/client/README.md) 通过 stdio JSON-RPC 驱动。它是 [`subagent-acp`](../subagent-acp/README.md) 之外的第二个进程外后端,差异在协议格式(wire format)和子进程契约:ACP(Agent Client Protocol)后端能驱动任何 Agent Client Protocol agent(智能体);本后端专门驱动 harness SDK 运行时(`dsh-jsonrpc-agent` bin 或打包后的可执行文件),因此子进程是一个完整的对等 harness,拥有由 `cordis.yml` 决定的组合、会话持久化、模型路由和工具。 ## 启动与所有权 diff --git a/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts b/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts index 5603e0e9a0..e42a76c90c 100644 --- a/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts +++ b/packages/subagent/subagent-dsh-sdk/tests/subagent-dsh-sdk.spec.ts @@ -24,7 +24,7 @@ import { type SdkRunSpec, } from '../src/run.ts' -const fakeRuntime = fileURLToPath(new URL('../../../sdk/sdk-client/tests/fake-runtime.ts', import.meta.url)) +const fakeRuntime = fileURLToPath(new URL('../../../scaffold/client/tests/fake-runtime.ts', import.meta.url)) /** A parent Agent stub. The SDK backend reads exactly one thing off it: the session header's cwd (the workspace its child inherits). */ const fakeParent = { id: 'parent', session: { header: { cwd: process.cwd() } } } as unknown as Agent diff --git a/packages/subagent/subagent-dsh-sdk/tsconfig.json b/packages/subagent/subagent-dsh-sdk/tsconfig.json index 79ff03f1a4..2b9aa29e21 100644 --- a/packages/subagent/subagent-dsh-sdk/tsconfig.json +++ b/packages/subagent/subagent-dsh-sdk/tsconfig.json @@ -27,10 +27,10 @@ "path": "../../core/session" }, { - "path": "../../sdk/sdk-client" + "path": "../../scaffold/client" }, { - "path": "../../sdk/sdk-protocol" + "path": "../../scaffold/protocol" }, { "path": "../subagent" diff --git a/packages/subagent/subagent-inprocess/tsconfig.json b/packages/subagent/subagent-inprocess/tsconfig.json index 22c00b3a94..23406e362e 100644 --- a/packages/subagent/subagent-inprocess/tsconfig.json +++ b/packages/subagent/subagent-inprocess/tsconfig.json @@ -39,7 +39,7 @@ "path": "../../support/invariants" }, { - "path": "../../ui/user-approval" + "path": "../../interaction/user-approval" } ] } diff --git a/packages/typert/generator/tests/type-model.spec.ts b/packages/typert/generator/tests/type-model.spec.ts index 40a91e3ef5..e1b5d99de9 100644 --- a/packages/typert/generator/tests/type-model.spec.ts +++ b/packages/typert/generator/tests/type-model.spec.ts @@ -419,7 +419,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { it('rejects relative imports across face boundaries', () => { const root = copyFixture('typert-relative-face-') - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') const source = readFileSync(sourcePath, 'utf8') .replace("from '@fixture/host'", "from '../../host/src/index.ts'") writeFileSync(sourcePath, source) @@ -435,7 +435,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { join(root, 'packages/host/src/private.ts'), 'export interface PrivateHost { readonly value: string }\n', ) - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') const source = readFileSync(sourcePath, 'utf8') .replace( "import type { HostAgent, Payload } from '@fixture/host'", @@ -458,7 +458,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { join(root, 'packages/host/src/private.ts'), 'export interface PrivateHost { readonly value: string }\n', ) - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') writeFileSync(sourcePath, [ readFileSync(sourcePath, 'utf8'), "export type { PrivateHost } from '@fixture/host/private'", @@ -472,7 +472,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { it('rejects cross-face namespace re-exports until the model has a namespace target', () => { const root = copyFixture('typert-namespace-reexport-') - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') writeFileSync(sourcePath, [ readFileSync(sourcePath, 'utf8'), "export type * as HostNamespace from '@fixture/host'", @@ -487,10 +487,10 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { it('ignores cross-face namespace exports that are not package exports', () => { const root = copyFixture('typert-private-namespace-reexport-') writeFileSync( - join(root, 'packages/client/src/internal.ts'), + join(root, 'packages/client', 'src/internal.ts'), "export type * as HiddenHostNamespace from '@fixture/host'\n", ) - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') writeFileSync(sourcePath, [ "import './internal.ts'", readFileSync(sourcePath, 'utf8'), @@ -503,7 +503,7 @@ describe('WorkspaceAnalyzer', { timeout: 60_000 }, () => { it('records public symbols from explicit cross-face star re-exports', () => { const root = copyFixture('typert-star-reexport-') - const sourcePath = join(root, 'packages/client/src/index.ts') + const sourcePath = join(root, 'packages/client', 'src/index.ts') writeFileSync( sourcePath, readFileSync(sourcePath, 'utf8') @@ -1138,7 +1138,7 @@ describe('WorkspaceTypertGenerator', { timeout: 60_000 }, () => { it('rejects a public Typert subpath that points outside the root-level face artifact', () => { const root = copyFixture('typert-artifact-path-') - const manifestPath = join(root, 'packages/client/package.json') + const manifestPath = join(root, 'packages/client', 'package.json') const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { exports: Record } @@ -1154,7 +1154,7 @@ describe('WorkspaceTypertGenerator', { timeout: 60_000 }, () => { it('rejects absent Typert exports and package file entries', () => { const noSubpathRoot = copyFixture('typert-missing-artifact-export-') - const noSubpathManifest = join(noSubpathRoot, 'packages/client/package.json') + const noSubpathManifest = join(noSubpathRoot, 'packages/client', 'package.json') const noSubpath = JSON.parse(readFileSync(noSubpathManifest, 'utf8')) as Record noSubpath.exports = './lib/index.js' writeFileSync(noSubpathManifest, `${JSON.stringify(noSubpath, null, 2)}\n`) @@ -1163,7 +1163,7 @@ describe('WorkspaceTypertGenerator', { timeout: 60_000 }, () => { ) const invalidSubpathRoot = copyFixture('typert-invalid-artifact-export-') - const invalidSubpathManifest = join(invalidSubpathRoot, 'packages/client/package.json') + const invalidSubpathManifest = join(invalidSubpathRoot, 'packages/client', 'package.json') const invalidSubpath = JSON.parse(readFileSync(invalidSubpathManifest, 'utf8')) as { exports: Record } @@ -1174,7 +1174,7 @@ describe('WorkspaceTypertGenerator', { timeout: 60_000 }, () => { ) const noFilesRoot = copyFixture('typert-missing-artifact-files-') - const noFilesManifest = join(noFilesRoot, 'packages/client/package.json') + const noFilesManifest = join(noFilesRoot, 'packages/client', 'package.json') const noFiles = JSON.parse(readFileSync(noFilesManifest, 'utf8')) as Record delete noFiles.files writeFileSync(noFilesManifest, `${JSON.stringify(noFiles, null, 2)}\n`) diff --git a/packages/ui/jsonrpc/README.i18n.yaml b/packages/ui/jsonrpc/README.i18n.yaml deleted file mode 100644 index d81f54f405..0000000000 --- a/packages/ui/jsonrpc/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/jsonrpc/README.md -README.md: 9cd4876b52f9527745b27041eb2555408c73fabd -README.zh.md: a2b979da448c04c0578382889be2ae3ea6076166 diff --git a/packages/ui/permission/README.i18n.yaml b/packages/ui/permission/README.i18n.yaml deleted file mode 100644 index cb48934488..0000000000 --- a/packages/ui/permission/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/permission/README.md -README.md: 4f7f560bb81eaad3b6b95b2742432fa252682d5a -README.zh.md: d45f89e243ce2d8f6bb08943fb7e776ced106b5a diff --git a/packages/ui/user-interaction/README.i18n.yaml b/packages/ui/user-interaction/README.i18n.yaml deleted file mode 100644 index 1da849ed9b..0000000000 --- a/packages/ui/user-interaction/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/ui/user-interaction/README.md -README.md: cba015e782623b3a5adf018303823577a0b96774 -README.zh.md: a5f944850c5ac4e05da59e8478167eef91796610 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 2c347613e8..08019b567e 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -275,7 +275,7 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { ProjectionChangeListener: 'change-feed listener contract is owned by packages/session/session-projection/src/index.ts', ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session/session-projection/src/index.ts', ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session/session-projection/src/index.ts', - CommandExecution: 'executor return contract is owned by packages/ui/commands/src/index.ts', + CommandExecution: 'executor return contract is owned by packages/interaction/commands/src/index.ts', TypertContribution: 'registry contribution contract is owned by packages/typert/registry/README.md', TypertFace: 'registry face identity is owned by packages/typert/registry/README.md', TypertPackageFilter: 'registry package query filter is owned by packages/typert/registry/README.md', @@ -295,10 +295,10 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts', InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md', InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md', - PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md', - PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md', - KnobState: 'projection unit state shape is owned by packages/ui/permission/README.md', - PermissionSelect: 'permissions projection payload is owned by packages/ui/permission/src/types.ts', + PresetOption: 'deployment menu metadata is owned by packages/interaction/permission/README.md', + PresetSpec: 'deployment preset composition is owned by packages/interaction/permission/README.md', + KnobState: 'projection unit state shape is owned by packages/interaction/permission/README.md', + PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts', PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', ResumeAgentOptions: 'agent resume contract is owned by packages/core/agent/README.md', Sandbox: 'external E2B SDK handle is owned by packages/e2b/e2b/README.md', diff --git a/scripts/gen-third-party-notices.spec.ts b/scripts/gen-third-party-notices.spec.ts index 8299114ff5..c9b1f41dbf 100644 --- a/scripts/gen-third-party-notices.spec.ts +++ b/scripts/gen-third-party-notices.spec.ts @@ -70,7 +70,7 @@ describe('tierExternalDeps', () => { it('keeps a package runtime when any shipping area declares it, and excludes workspace links', () => { const { manifests, names } = workspace({ 'package.json': { devDependencies: { shared: '^1' } }, - 'packages/ui/tui/package.json': { name: '@deepseek-ai/dsh-tui', dependencies: { shared: '^1', '@deepseek-ai/dsh-cli': 'workspace:^' } }, + 'packages/interaction/tui/package.json': { name: '@deepseek-ai/dsh-tui', dependencies: { shared: '^1', '@deepseek-ai/dsh-cli': 'workspace:^' } }, 'apps/cli/package.json': { name: '@deepseek-ai/dsh-cli' }, }) diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index b59aafe5f0..06475808e1 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -154,7 +154,7 @@ const TOOL_PACKAGES: ToolPackage[] = [ { pkg: '@deepseek-ai/dsh-tool-ask-user', dir: 'tool-ask-user', - source: 'packages/ui/tool-ask-user/src/index.ts', + source: 'packages/interaction/tool-ask-user/src/index.ts', requires: ['ctx.tools', 'ctx.userInteraction'], writes: ['tool/call', 'tool/result after a UI/provider answers the question'], async mount(ctx) { diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 258b70f87d..d99889d28b 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -619,7 +619,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate { 'apps/cli/tests/built-bin.e2e.ts', 'packages/examples/acp-demo/tests/built-bin.e2e.ts', 'packages/host/directory-picker-native/tests/built-worker.e2e.ts', - 'packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts', + 'packages/scaffold/server/tests/built-scope-carrier.e2e.ts', 'packages/subagent/subagent-codex/tests/loader-composition.e2e.ts', 'packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts', 'packages/api/remotes/tests/built-lib.e2e.ts', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index e4eaf15398..903172e48f 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -234,32 +234,32 @@ { "doc": "docs/core-data-structures/commands.md", "symbol": "CommandInputDescriptor", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/commands.md", "symbol": "CommandDefinition", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/commands.md", "symbol": "CommandInvocation", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/commands.md", "symbol": "CommandResult", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/commands.md", "symbol": "CommandDescriptor", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/commands.md", "symbol": "ParsedCommand", - "source": "packages/ui/commands/src/index.ts" + "source": "packages/interaction/commands/src/index.ts" }, { "doc": "docs/core-data-structures/system-prompt.md", @@ -733,62 +733,62 @@ { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionOption", - "source": "packages/ui/user-interaction/src/types.ts" + "source": "packages/interaction/user-interaction/src/types.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionIntent", - "source": "packages/ui/user-interaction/src/types.ts" + "source": "packages/interaction/user-interaction/src/types.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionItem", - "source": "packages/ui/user-interaction/src/types.ts" + "source": "packages/interaction/user-interaction/src/types.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionRequest", - "source": "packages/ui/user-interaction/src/index.ts" + "source": "packages/interaction/user-interaction/src/index.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionAnswerItem", - "source": "packages/ui/user-interaction/src/types.ts" + "source": "packages/interaction/user-interaction/src/types.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionAnswer", - "source": "packages/ui/user-interaction/src/types.ts" + "source": "packages/interaction/user-interaction/src/types.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "UserInteractionProvider", - "source": "packages/ui/user-interaction/src/index.ts" + "source": "packages/interaction/user-interaction/src/index.ts" }, { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "UserInteractionError", - "source": "packages/ui/user-interaction/src/index.ts" + "source": "packages/interaction/user-interaction/src/index.ts" }, { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalRequestId", - "source": "packages/ui/user-approval/src/types.ts" + "source": "packages/interaction/user-approval/src/types.ts" }, { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalOutcome", - "source": "packages/ui/user-approval/src/types.ts" + "source": "packages/interaction/user-approval/src/types.ts" }, { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalPolicy", - "source": "packages/ui/user-approval/src/index.ts" + "source": "packages/interaction/user-approval/src/index.ts" }, { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalRequest", - "source": "packages/ui/user-approval/src/index.ts" + "source": "packages/interaction/user-approval/src/index.ts" }, { "doc": "docs/core-data-structures/bash.md", diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 5930d39e26..a0eec4960f 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -103,12 +103,12 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/e2b/subprocess-e2b': { kind: 'indirect', reason: 'The remote spawn backend delegates model rendering to consumer seams such as the bash executor family.' }, 'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' }, 'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' }, - 'packages/sdk/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' }, - 'packages/sdk/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' }, - 'packages/sdk/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' }, - 'packages/sdk/sdk-client': { kind: 'none', reason: 'Client-process library; the model surface lives in the spawned runtime\'s composed plugins.' }, - 'packages/sdk/sdk-protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model surface.' }, - 'packages/sdk/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' }, + 'packages/scaffold/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' }, + 'packages/scaffold/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' }, + 'packages/scaffold/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' }, + 'packages/scaffold/client': { kind: 'none', reason: 'Client-process library; the model surface lives in the spawned runtime\'s composed plugins.' }, + 'packages/scaffold/protocol': { kind: 'none', reason: 'Client-facing wire library; the runtime plugins behind the serving entry own the model surface.' }, + 'packages/scaffold/telemetry': { kind: 'none', reason: 'The launcher-side reporter sends developer-cycle telemetry and registers no live agent or model surface.' }, 'packages/session/session-projection': { kind: 'none', reason: 'The projection registry serves client-facing read models of already-logged session state and registers no model surface.' }, 'packages/session/session-projection-cache': { kind: 'none', reason: 'The persisted cache accelerates host-side cold reads of projection state and registers no model surface.' }, 'packages/session-query/session-query': { kind: 'none', reason: 'The trusted query service exposes cloned records only to callers and registers no model surface.' }, @@ -138,10 +138,10 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and control-surface plugins own all model rendering over the task registry.' }, 'packages/tasks/tasks-local': { kind: 'indirect', reason: 'The registry backend delegates model rendering to producer plugins and dsh-tool-tasks.' }, 'packages/examples/acp-demo': { kind: 'indirect', reason: 'The app bundle delegates request composition to dsh-agent-spine-demo and dsh-acp.' }, - 'packages/ui/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' }, + 'packages/boot/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' }, 'packages/examples/jsonrpc-demo': { kind: 'indirect', reason: 'Only the externally configured plugin tree contributes model context.' }, - 'packages/ui/permission': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' }, - 'packages/ui/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' }, + 'packages/interaction/permission': { kind: 'indirect', reason: 'The service writes mechanism events rendered by dsh-user-approval and dsh-tool-bash.' }, + 'packages/interaction/user-interaction': { kind: 'indirect', reason: 'Model-facing consumers render provider answers and seam errors.' }, 'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' }, 'packages/util/retention': { kind: 'indirect', reason: 'Only retention consumers render retained content and omission metadata.' }, 'packages/util/native-command': { kind: 'none', reason: 'The host-side subprocess runner registers no model surface.' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index e09ab93743..b748020e11 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -70,12 +70,13 @@ "@deepseek-ai/dsh-llm/brand": ["./packages/llm/llm/src/brand.ts"], "@deepseek-ai/dsh-llm-retry/types": ["./packages/llm/llm-retry/src/types.ts"], "@deepseek-ai/dsh-llm/message": ["./packages/llm/llm/src/message.ts"], - "@deepseek-ai/dsh-commands/brand": ["./packages/ui/commands/src/brand.ts"], + "@deepseek-ai/dsh-commands/brand": ["./packages/interaction/commands/src/brand.ts"], "@deepseek-ai/dsh-compact/checkpoint": ["./packages/compact/compact/src/checkpoint.ts"], "@deepseek-ai/dsh-tools/presentation": ["./packages/core/tools/src/presentation.ts"], "@deepseek-ai/dsh-tool-subagent-control/list-agents": ["./packages/subagent/tool-subagent-control/src/list-agents.ts"], - "@deepseek-ai/dsh-user-approval/types": ["./packages/ui/user-approval/src/types.ts"], - "@deepseek-ai/dsh-user-interaction/types": ["./packages/ui/user-interaction/src/types.ts"], + "@deepseek-ai/dsh-user-approval/types": ["./packages/interaction/user-approval/src/types.ts"], + "@deepseek-ai/dsh-user-interaction/types": ["./packages/interaction/user-interaction/src/types.ts"], + "@deepseek-ai/dsh-agent/brand": ["./packages/core/agent/src/brand.ts"], "@deepseek-ai/dsh-agent/invariant": ["./packages/core/agent/src/invariant.ts"], "@deepseek-ai/dsh-scope/invariant": ["./packages/core/scope/src/invariant.ts"], "@deepseek-ai/dsh-agent-loop/invariant": ["./packages/core/agent-loop/src/invariant.ts"], @@ -113,8 +114,9 @@ "./packages/acp/*/src/invariant.ts", "./packages/storage/*/src/invariant.ts", "./packages/workspace/*/src/invariant.ts", - "./packages/sdk/*/src/invariant.ts", - "./packages/ui/*/src/invariant.ts", + "./packages/scaffold/*/src/invariant.ts", + "./packages/interaction/*/src/invariant.ts", + "./packages/boot/*/src/invariant.ts", "./packages/examples/*/src/invariant.ts", "./packages/util/*/src/invariant.ts", "./packages/mcp/*/src/invariant.ts", @@ -179,6 +181,12 @@ "@deepseek-ai/dsh-client-ui-models": ["./packages/client/ui-models/src"], "@deepseek-ai/dsh-client-locale": ["./packages/client/locale/src"], "@deepseek-ai/dsh-client-web": ["./packages/client/web/src"], + // scaffold/ folders are role-named without the sdk- prefix (folder ≠ npm + // suffix), so the generic wildcard cannot map these three; the npm-side + // renames that restore symmetry are FIXME-tracked in the regrouping note. + "@deepseek-ai/dsh-sdk-client": ["./packages/scaffold/client/src"], + "@deepseek-ai/dsh-sdk-protocol": ["./packages/scaffold/protocol/src"], + "@deepseek-ai/dsh-jsonrpc": ["./packages/scaffold/server/src"], "@deepseek-ai/dsh-*": [ "./packages/core/*/src", "./packages/prompt/*/src", @@ -215,8 +223,9 @@ "./packages/acp/*/src", "./packages/storage/*/src", "./packages/workspace/*/src", - "./packages/sdk/*/src", - "./packages/ui/*/src", + "./packages/scaffold/*/src", + "./packages/interaction/*/src", + "./packages/boot/*/src", "./packages/examples/*/src", "./packages/util/*/src", "./packages/mcp/*/src", diff --git a/tsconfig.host.json b/tsconfig.host.json index 0f44c4b7de..c1cc0705cf 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -138,7 +138,7 @@ { "path": "./packages/session/session-telemetry-otel" }, { "path": "./packages/core/system-prompt" }, { "path": "./packages/core/agent" }, - { "path": "./packages/ui/commands" }, + { "path": "./packages/interaction/commands" }, { "path": "./packages/goal/goal" }, { "path": "./packages/goal/tool-goal" }, { "path": "./packages/goal/goal-session" }, @@ -147,15 +147,15 @@ { "path": "./packages/context/time-context" }, { "path": "./packages/context/tmux-context" }, { "path": "./packages/context/session-reference" }, - { "path": "./packages/ui/user-interaction" }, - { "path": "./packages/ui/user-approval" }, - { "path": "./packages/ui/permission" }, + { "path": "./packages/interaction/user-interaction" }, + { "path": "./packages/interaction/user-approval" }, + { "path": "./packages/interaction/permission" }, { "path": "./packages/core/tools" }, { "path": "./packages/skill/skill" }, { "path": "./packages/skill/skill-badge" }, { "path": "./packages/skill/skill-local" }, { "path": "./packages/skill/tool-skill" }, - { "path": "./packages/ui/tool-ask-user" }, + { "path": "./packages/interaction/tool-ask-user" }, { "path": "./packages/context/workspace-context" }, { "path": "./packages/core/agent-loop" }, { "path": "./packages/llm/llm-retry" }, @@ -212,8 +212,8 @@ { "path": "./packages/bundle/base" }, { "path": "./packages/bundle/headless" }, { "path": "./packages/bundle/web-app" }, - { "path": "./packages/ui/app-boot" }, - { "path": "./packages/ui/jsonrpc" }, + { "path": "./packages/boot/app-boot" }, + { "path": "./packages/scaffold/server" }, { "path": "./packages/examples/jsonrpc-demo" }, { "path": "./packages/support/llm-replay" }, { "path": "./packages/typert/generator" }, @@ -259,12 +259,12 @@ { "path": "./packages/host/directory-picker-native" }, { "path": "./packages/host/frontend-static" }, { "path": "./packages/host/webserver" }, - { "path": "./packages/sdk/sdk-client" }, - { "path": "./packages/sdk/helper" }, - { "path": "./packages/sdk/sdk-protocol" }, - { "path": "./packages/sdk/scripts" }, - { "path": "./packages/sdk/create-sdk" }, - { "path": "./packages/sdk/telemetry" }, + { "path": "./packages/scaffold/client" }, + { "path": "./packages/scaffold/helper" }, + { "path": "./packages/scaffold/protocol" }, + { "path": "./packages/scaffold/scripts" }, + { "path": "./packages/scaffold/create-sdk" }, + { "path": "./packages/scaffold/telemetry" }, { "path": "./packages/lsp/lsp" }, { "path": "./packages/lsp/lsp-local" }, { "path": "./packages/lsp/tool-lsp" }, diff --git a/vendor/README.md b/vendor/README.md index 4bf1d43f46..02e33ea723 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -37,11 +37,11 @@ Keep this log exhaustive — every divergence from upstream must be listed. 5. **`schemastery/tsdown.config.ts` and `logger-console/tsdown.config.ts`**: ours, not upstream files — per-package build-shape overrides (dual ESM+CJS output; separate node/browser entries) for the repo-root tsdown build. They read the JS emitted under `lib/types` and then write the publish runtime entries under `lib/`. Like the regenerated tsconfigs, they are not part of the upstream sync surface. 6. **`cordis/src/fiber.ts` lifecycle hardening**: locally closes three reentrant disposal gaps. An effect's owner-list wrapper is registered before its setup body runs, so an unload begun from inside setup awaits setup and every collected cleanup; synchronous setup failure removes the wrapper and rolls back collected cleanup. Async cleanup stays owner-visible until quiescence, and Cordis's internal effect composition joins an already-running cleanup while repeated public disposer calls retain their upstream single-shot result. Effect creation is rejected while the owner is `UNLOADING` (while `PENDING` and `LOADING` remain legal), preventing cleanup-time registrations from escaping the unload snapshot. Child fibers register and receive their parent-owned disposer before `internal/plugin` publication, resolve dependency declarations added by that notification before activation, drain effects attached while pending, skip plugin execution when reentrant disposal invalidates the load epoch before its first checkpoint, and contain teardown-notification failures per observer so one callback cannot starve peers or interrupt ownership cleanup. `Fiber.update()` returns its `internal/update` waterfall result, allowing Loader callers to await a restart while preserving synchronous config validation. 7. **`cordis/src/*.ts` JSDoc enrichment**: added `@param`/`@returns` tags and contract documentation (disposal semantics, waterfall veto, bail conditions, error cases) across the public plugin-author surface — `Context` (class, statics, and the `Context` interface properties incl. `root`), `EventsService`, `Fiber`, `RegistryService`, `ReflectService`, `Service`, `LoggerService` and their `declare module './context.ts'` overloads. Comment-only; no code changes. Motivation: the website API-reference generator renders these docs and hard-errors on undocumented members. Retire this entry when the enrichment is upstreamed to the fork. -8. **Transactional Loader/Include config reconciliation**: Loader imports a changed entry name before disposal, awaits lifecycle settlement, and restores the previous plugin or config when candidate application fails. Loader settlement rechecks service-gated fibers after current tasks drain, rejects failures, and leaves fibers with absent dependencies pending. Group updates start candidates concurrently, await every outcome, undo changes and additions on failure, await removal, preserve programmatic option identity, and persist direct or tree-level mutations only after success. Include reads and validates detached candidate content, applies patches to a clone, reconciles the tree, and only then commits its cached content/data; direct refresh failures propagate for the caller to contain. A non-array parse is invalid, patches re-apply on every file or Include-config update, an omitted patch list clears the overlay, and initial content falls back to `initial` only on `ENOENT`. Covered by `packages/ui/app-boot/tests/config-reload.spec.ts` and `packages/host/webserver/tests/webserver.spec.ts`. -9. **`hmr/src/index.ts` exact config watching**: `registerConfig()` watches one absolute config path outside module roots, including a path under missing parents, serializes and coalesces refreshes, and returns an async disposer that closes the watcher and drains active work. Refresh failures are normalized to `Error`, logged, and broadcast through the parallel `hmr/config-update-failed` event; observer failures are contained. Config-file changes discovered by the ordinary HMR watcher use the same serialized path. Covered by `packages/ui/app-boot/tests/hmr-config.spec.ts`. -10. **`loader/src/repository.ts`, `loader/tsdown.config.ts`, and the `@cordisjs/plugin-loader/repository` export**: the Node-only `RepositoryCache` installs one exact dependency specifier through the bundled `pnpm@11.7.0`, single-flights callers, and atomically publishes only a prepared package plus marker under the specifier hash. The subpath stays out of the browser-reachable Loader entry. Identical specifiers permanently reuse that entry; callers change the ref/specifier for another generation. The isolated workspace permits dependency build scripts because a configured repository is executable code, while the child drops ambient credential-shaped variables. Covered by `packages/ui/app-boot/tests/repository-cache.spec.ts`, including a keyless local-Git prepare run through the bundled pnpm. +8. **Transactional Loader/Include config reconciliation**: Loader imports a changed entry name before disposal, awaits lifecycle settlement, and restores the previous plugin or config when candidate application fails. Loader settlement rechecks service-gated fibers after current tasks drain, rejects failures, and leaves fibers with absent dependencies pending. Group updates start candidates concurrently, await every outcome, undo changes and additions on failure, await removal, preserve programmatic option identity, and persist direct or tree-level mutations only after success. Include reads and validates detached candidate content, applies patches to a clone, reconciles the tree, and only then commits its cached content/data; direct refresh failures propagate for the caller to contain. A non-array parse is invalid, patches re-apply on every file or Include-config update, an omitted patch list clears the overlay, and initial content falls back to `initial` only on `ENOENT`. Covered by `packages/boot/app-boot/tests/config-reload.spec.ts` and `packages/host/webserver/tests/webserver.spec.ts`. +9. **`hmr/src/index.ts` exact config watching**: `registerConfig()` watches one absolute config path outside module roots, including a path under missing parents, serializes and coalesces refreshes, and returns an async disposer that closes the watcher and drains active work. Refresh failures are normalized to `Error`, logged, and broadcast through the parallel `hmr/config-update-failed` event; observer failures are contained. Config-file changes discovered by the ordinary HMR watcher use the same serialized path. Covered by `packages/boot/app-boot/tests/hmr-config.spec.ts`. +10. **`loader/src/repository.ts`, `loader/tsdown.config.ts`, and the `@cordisjs/plugin-loader/repository` export**: the Node-only `RepositoryCache` installs one exact dependency specifier through the bundled `pnpm@11.7.0`, single-flights callers, and atomically publishes only a prepared package plus marker under the specifier hash. The subpath stays out of the browser-reachable Loader entry. Identical specifiers permanently reuse that entry; callers change the ref/specifier for another generation. The isolated workspace permits dependency build scripts because a configured repository is executable code, while the child drops ambient credential-shaped variables. Covered by `packages/boot/app-boot/tests/repository-cache.spec.ts`, including a keyless local-Git prepare run through the bundled pnpm. 11. **Vendored Node-compatible TypeScript**: marked erased imports explicitly across `cordis`, `loader`, `include`, `hmr`, and `schemastery` so Node's native TypeScript transform does not request types as runtime exports. Schemastery's source uses an ESM default export and its package declares `type: module`; its built ESM/CJS entries retain explicit `.mjs`/`.cjs` extensions. -12. **`include/src/index.ts` patch-semantics export**: extracted the private `applyPatches` body into the exported pure function `applyEntryPatches(data, patches, warn)` (the method delegates to it) and exported the `!!js` YAML dialect as `entryListSchema`, so `dsh --dump-config` composes and prints exactly what the include would mount without booting a tree. Behavior-preserving for mounting; the extraction exists because config tooling must never reimplement (and drift from) the patch algorithm. `applyEntryPatches` also indexes each `insert`ed entry as it is added, so a later patch in the same list can configure or disable a row an earlier patch inserted; upstream built the id index once before the patch loop, leaving inserted rows silently unpatchable. That matters because `dsh` composes an empty profile root with each bundle's patch layer, the profile's and the home-level `cordis.patch.yml`, and any `--patch` overlays as sibling patch lists at one include level — patches never cross an include boundary, so surface-only rows would otherwise be unreachable from user config. Covered by `packages/ui/app-boot/tests/config-reload.spec.ts`. +12. **`include/src/index.ts` patch-semantics export**: extracted the private `applyPatches` body into the exported pure function `applyEntryPatches(data, patches, warn)` (the method delegates to it) and exported the `!!js` YAML dialect as `entryListSchema`, so `dsh --dump-config` composes and prints exactly what the include would mount without booting a tree. Behavior-preserving for mounting; the extraction exists because config tooling must never reimplement (and drift from) the patch algorithm. `applyEntryPatches` also indexes each `insert`ed entry as it is added, so a later patch in the same list can configure or disable a row an earlier patch inserted; upstream built the id index once before the patch loop, leaving inserted rows silently unpatchable. That matters because `dsh` composes an empty profile root with each bundle's patch layer, the profile's and the home-level `cordis.patch.yml`, and any `--patch` overlays as sibling patch lists at one include level — patches never cross an include boundary, so surface-only rows would otherwise be unreachable from user config. Covered by `packages/boot/app-boot/tests/config-reload.spec.ts`. 13. **`include/src/index.ts` serialized child-tree mutation and `hmr/src/index.ts` main-watcher initial-scan suppression**: every Include child-tree mutation (initial apply, refresh, `internal/update` patch re-application) runs through one per-Include queue, because the group's transactional `update` is not reentrant — two concurrent applies interleave create and rollback on the same entries and strand the Include fiber without ever settling. The HMR main watcher passes `ignoreInitial: true`: the initial scan re-announced files boot had just consumed, and its `add` for a config file refreshed an Include mid-initial-apply; once serialized, a failing initial apply's rollback disposed HMR, whose teardown drain waited on the queued refresh sitting behind that same apply — a deadlock that exited 13 with no diagnostic. `registerConfig()` keeps its own `ignoreInitial: false` watcher because a user patch layer present at registration must apply once. Covered by the patch-overlay boot-failure built-bin case in `apps/cli/tests/built-bin.e2e.ts`. 14. **`include/src/index.ts` `writeTask` type**: widened the optional `writeTask?: NodeJS.Timeout` property to `NodeJS.Timeout | undefined` — the debounced writer assigns `undefined` on flush, which `exactOptionalPropertyTypes` rejects on a plain optional. Type-only; no behavior change. diff --git a/vitest.config.ts b/vitest.config.ts index 29ad406e75..4456782519 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -32,8 +32,8 @@ const windowsUnsupportedPackages = process.platform === 'win32' 'packages/subprocess/*', 'packages/pty/pty-local', 'packages/sandbox/sandbox-local', - 'packages/sdk/create-sdk', - 'packages/sdk/helper', + 'packages/scaffold/create-sdk', + 'packages/scaffold/helper', ] : [] @@ -83,7 +83,7 @@ const processBoundTests = [ 'packages/subprocess/subprocess-local/tests/spawn.spec.ts', 'packages/context/time-context/tests/time-context.spec.ts', 'packages/llm/llm-pi-ai/tests/adapter.spec.ts', - 'packages/ui/app-boot/tests/app-boot.spec.ts', + 'packages/boot/app-boot/tests/app-boot.spec.ts', 'packages/workflow/workflow-workerthread/tests/session.spec.ts', ] @@ -222,8 +222,8 @@ export default defineConfig({ // Projection/command round: executor lifecycle branches and the // registry's drive tails need the same maturing lanes. TODO(gui): // cover and remove with the client test lane above. - 'packages/ui/commands/src/index.ts', - 'packages/ui/commands/src/invariant.ts', + 'packages/interaction/commands/src/index.ts', + 'packages/interaction/commands/src/invariant.ts', 'packages/session/session-projection/src/index.ts', ...windowsUnsupportedPackages.map(path => `${path}/src/**/*.ts`), ...windowsCoverageExclusions, diff --git a/vitest.snapshot.config.ts b/vitest.snapshot.config.ts index 049688a0ec..cf9cae0780 100644 --- a/vitest.snapshot.config.ts +++ b/vitest.snapshot.config.ts @@ -51,7 +51,7 @@ export default defineConfig({ ...(process.env.DSH_EXAMPLE_MODE === 'lib' ? ['apps/web/tests/**/*.snapshot.ts'] : []), 'apps/cli/tests/**/*.snapshot.ts', 'examples/*/tests/**/*.snapshot.ts', - 'packages/sdk/*/tests/**/*.snapshot.ts', + 'packages/scaffold/*/tests/**/*.snapshot.ts', ], // Replay never writes committed outputs and every scenario owns its // mutable runtime state (the subprocess suites use a unique temp dir and From 2a40cbf8efb0a9e9e3690b8c7ad0eb65a8d319c5 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 03:40:42 +0800 Subject: [PATCH 04/19] refactor(packages): merge timeout/ into guard/, rename cordis/ to self-modification/ git mv timeout-policy beside repeat-tool-guard (both are loop-hygiene policies on the tool-execution pipeline, and the timeout/ group name collided with util/timeout) and tool-cordis into self-modification/ (naming the role the framework name obscured). Merged/renamed group README triplets, tsconfig globs, generator sources, hierarchy tables, catalogs, and the timeout-policy design note's group references follow. Adds the fifth FIXME marker (dsh-timeout-guard, recorded as a suggestion to settle at resolution time). guard + self-modification suites: 197 passed. --- ...-07-28-dsh-guided-skill-session-commands.i18n.yaml | 6 +++--- .../2026-07-28-dsh-guided-skill-session-commands.md | 2 +- ...2026-07-28-dsh-guided-skill-session-commands.zh.md | 2 +- .../2026-07-07-tool-call-timeout-policy.i18n.yaml | 4 ++-- .../2026-07-07-tool-call-timeout-policy.md | 4 ++-- .../2026-07-07-tool-call-timeout-policy.zh.md | 4 ++-- ...2026-07-19-cooperative-tool-cancellation.i18n.yaml | 4 ++-- .../2026-07-19-cooperative-tool-cancellation.md | 2 +- .../2026-07-19-cooperative-tool-cancellation.zh.md | 2 +- ...-package-manager-native-repository-cache.i18n.yaml | 4 ++-- ...6-07-30-package-manager-native-repository-cache.md | 2 +- ...7-30-package-manager-native-repository-cache.zh.md | 2 +- ...26-07-08-self-referential-cordis-toolset.i18n.yaml | 4 ++-- .../2026-07-08-self-referential-cordis-toolset.md | 2 +- .../2026-07-08-self-referential-cordis-toolset.zh.md | 2 +- ...2026-07-31-even-out-shipped-tool-rosters.i18n.yaml | 4 ++-- .../2026-07-31-even-out-shipped-tool-rosters.md | 2 +- .../2026-07-31-even-out-shipped-tool-rosters.zh.md | 2 +- .../2026-07-29-shared-base-config-overlays.i18n.yaml | 4 ++-- .../2026-07-29-shared-base-config-overlays.md | 4 ++-- .../2026-07-29-shared-base-config-overlays.zh.md | 4 ++-- ...2026-07-04-prune-dead-core-spine-surface.i18n.yaml | 4 ++-- .../2026-07-04-prune-dead-core-spine-surface.md | 2 +- .../2026-07-04-prune-dead-core-spine-surface.zh.md | 2 +- AGENTS.md | 6 +++--- README.i18n.yaml | 4 ++-- README.md | 2 +- README.zh.md | 2 +- docs/capability-seams.md | 2 +- docs/tool-catalog.md | 6 +++--- packages/README.i18n.yaml | 4 ++-- packages/README.md | 5 ++--- packages/README.zh.md | 5 ++--- packages/boot/app-boot/src/index.ts | 2 +- packages/cordis/README.md | 10 ---------- packages/cordis/README.zh.md | 10 ---------- packages/guard/README.i18n.yaml | 4 ++-- packages/guard/README.md | 3 ++- packages/guard/README.zh.md | 3 ++- .../timeout-policy/README.i18n.yaml | 2 +- packages/{timeout => guard}/timeout-policy/README.md | 0 .../{timeout => guard}/timeout-policy/README.zh.md | 0 .../{timeout => guard}/timeout-policy/package.json | 0 .../{timeout => guard}/timeout-policy/src/index.ts | 6 ++++++ .../timeout-policy/src/invariant.ts | 0 .../timeout-policy/tests/timeout-policy.spec.ts | 0 .../{timeout => guard}/timeout-policy/tsconfig.json | 0 packages/interaction/permission/README.i18n.yaml | 4 ++-- .../interaction/user-interaction/README.i18n.yaml | 4 ++-- packages/scaffold/protocol/README.i18n.yaml | 4 ++-- packages/scaffold/server/README.i18n.yaml | 4 ++-- .../{cordis => self-modification}/README.i18n.yaml | 6 +++--- packages/self-modification/README.md | 10 ++++++++++ packages/self-modification/README.zh.md | 10 ++++++++++ .../repository-plugin/README.i18n.yaml | 2 +- .../repository-plugin/README.md | 0 .../repository-plugin/README.zh.md | 0 .../repository-plugin/package.json | 0 .../repository-plugin/src/bin.ts | 0 .../repository-plugin/src/format.ts | 0 .../repository-plugin/src/index.ts | 0 .../repository-plugin/src/invariant.ts | 0 .../repository-plugin/src/mcp.ts | 0 .../repository-plugin/src/source.ts | 2 +- .../repository-plugin/tests/mcp-format.spec.ts | 0 .../repository-plugin/tests/repository-plugin.spec.ts | 0 .../repository-plugin/tsconfig.json | 0 .../repository-plugin/tsdown.config.ts | 0 .../tool-cordis/README.i18n.yaml | 2 +- .../tool-cordis/README.md | 0 .../tool-cordis/README.zh.md | 0 .../tool-cordis/package.json | 0 .../tool-cordis/src/api-catalog.ts | 0 .../tool-cordis/src/fiber-state.ts | 0 .../tool-cordis/src/guard.ts | 0 .../tool-cordis/src/index.ts | 0 .../tool-cordis/src/inspect.ts | 0 .../tool-cordis/src/invariant.ts | 0 .../tool-cordis/src/mount.ts | 0 .../tool-cordis/src/present.ts | 0 .../tool-cordis/src/sandbox.ts | 0 .../tool-cordis/tests/cordis-lifecycle.spec.ts | 0 .../tool-cordis/tests/cross-mount.spec.ts | 0 .../tool-cordis/tests/helpers.ts | 0 .../tool-cordis/tests/inspect.spec.ts | 0 .../tool-cordis/tests/integration.spec.ts | 0 .../tool-cordis/tests/mount.spec.ts | 0 .../tool-cordis/tests/present.spec.ts | 0 .../tool-cordis/tests/sandbox-context.spec.ts | 0 .../tool-cordis/tests/tool-cordis.spec.ts | 0 .../tool-cordis/tests/unmount-hmr.spec.ts | 0 .../tool-cordis/tsconfig.json | 0 packages/timeout/README.i18n.yaml | 6 ------ packages/timeout/README.md | 11 ----------- packages/timeout/README.zh.md | 11 ----------- .../typert/generator/tests/cordis-catalog.spec.ts | 2 +- packages/web/tool-web/README.i18n.yaml | 4 ++-- packages/web/tool-web/README.md | 4 ++-- packages/web/tool-web/README.zh.md | 4 ++-- packages/web/tool-web/tsconfig.json | 2 +- packages/web/web-fetch-local/README.i18n.yaml | 4 ++-- packages/web/web-fetch-local/README.md | 2 +- packages/web/web-fetch-local/README.zh.md | 2 +- scripts/gen-cordis-catalog.ts | 2 +- scripts/gen-tool-catalog.ts | 2 +- tsconfig.base.json | 6 ++---- tsconfig.host.json | 6 +++--- 107 files changed, 121 insertions(+), 145 deletions(-) delete mode 100644 packages/cordis/README.md delete mode 100644 packages/cordis/README.zh.md rename packages/{timeout => guard}/timeout-policy/README.i18n.yaml (79%) rename packages/{timeout => guard}/timeout-policy/README.md (100%) rename packages/{timeout => guard}/timeout-policy/README.zh.md (100%) rename packages/{timeout => guard}/timeout-policy/package.json (100%) rename packages/{timeout => guard}/timeout-policy/src/index.ts (91%) rename packages/{timeout => guard}/timeout-policy/src/invariant.ts (100%) rename packages/{timeout => guard}/timeout-policy/tests/timeout-policy.spec.ts (100%) rename packages/{timeout => guard}/timeout-policy/tsconfig.json (100%) rename packages/{cordis => self-modification}/README.i18n.yaml (55%) create mode 100644 packages/self-modification/README.md create mode 100644 packages/self-modification/README.zh.md rename packages/{cordis => self-modification}/repository-plugin/README.i18n.yaml (77%) rename packages/{cordis => self-modification}/repository-plugin/README.md (100%) rename packages/{cordis => self-modification}/repository-plugin/README.zh.md (100%) rename packages/{cordis => self-modification}/repository-plugin/package.json (100%) rename packages/{cordis => self-modification}/repository-plugin/src/bin.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/src/format.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/src/index.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/src/invariant.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/src/mcp.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/src/source.ts (98%) rename packages/{cordis => self-modification}/repository-plugin/tests/mcp-format.spec.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/tests/repository-plugin.spec.ts (100%) rename packages/{cordis => self-modification}/repository-plugin/tsconfig.json (100%) rename packages/{cordis => self-modification}/repository-plugin/tsdown.config.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/README.i18n.yaml (78%) rename packages/{cordis => self-modification}/tool-cordis/README.md (100%) rename packages/{cordis => self-modification}/tool-cordis/README.zh.md (100%) rename packages/{cordis => self-modification}/tool-cordis/package.json (100%) rename packages/{cordis => self-modification}/tool-cordis/src/api-catalog.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/fiber-state.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/guard.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/index.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/inspect.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/invariant.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/mount.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/present.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/src/sandbox.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/cordis-lifecycle.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/cross-mount.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/helpers.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/inspect.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/integration.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/mount.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/present.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/sandbox-context.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/tool-cordis.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tests/unmount-hmr.spec.ts (100%) rename packages/{cordis => self-modification}/tool-cordis/tsconfig.json (100%) delete mode 100644 packages/timeout/README.i18n.yaml delete mode 100644 packages/timeout/README.md delete mode 100644 packages/timeout/README.zh.md diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml index 8feded0ad4..a9e56019b3 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-dsh-guided-skill-session-commands.md -2026-07-28-dsh-guided-skill-session-commands.md: 454d090a55db8987f8e4987aba67deff1b21b1a0 -2026-07-28-dsh-guided-skill-session-commands.zh.md: 64dad4e39c6fd3e3341313c3f8dd2b04947695cf +# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md +2026-07-28-dsh-guided-skill-session-commands.md: 606513919a927af3bb7e9b74544fd8df9a1682ce +2026-07-28-dsh-guided-skill-session-commands.zh.md: 39af4a3a554ba3053f25b3d034346d3e1f4ce237 diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md index 454d090a55..606513919a 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md @@ -25,7 +25,7 @@ The `dsh-migrate` skill is bundled under `skills/` (shipped through `DSH_BUNDLED `apps/cli/tests/args.spec.ts` gains routing for `migrate`/`upgrade` (bare discriminant) and exit-1 for every leaked option on either side of each subcommand. -`packages/ui/tui/tests/tui.spec.ts` gains two fake-terminal cases in the existing skill describe block: `config.initialSkill` set delivers the rendered skill body as the first turn with no user input, and an unknown initial skill reports a notice without sending. `runSkillSession` itself is composition inside the module's `v8 ignore` block, like `runTui`/`runMeta`. +`packages/interaction/tui/tests/tui.spec.ts` gains two fake-terminal cases in the existing skill describe block: `config.initialSkill` set delivers the rendered skill body as the first turn with no user input, and an unknown initial skill reports a notice without sending. `runSkillSession` itself is composition inside the module's `v8 ignore` block, like `runTui`/`runMeta`. No keyless PTY snapshot: per the maintainer's scope call for this change, unit coverage plus interactive verification suffices, and the seed rides the already-snapshotted `/skill:` render path. Both commands were verified interactively in tmux from a scratch cwd: `dsh migrate` loaded `dsh-migrate` and asked which source agent; `dsh upgrade` loaded `dsh-upgrade`, which pulled in `dsh-customize` and began checkout discovery. diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md index 64dad4e39c..39af4a3a55 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md @@ -25,7 +25,7 @@ Archived: 2026-08-03 `apps/cli/tests/args.spec.ts` 新增 `migrate`/`upgrade` 的路由(裸判别式),以及每个子命令两侧任一泄漏选项的退出码 1。 -`packages/ui/tui/tests/tui.spec.ts` 在既有 skill describe 块中新增两个伪终端用例:设置 `config.initialSkill` 时无需用户输入即把渲染后的 skill 正文作为首轮投递;未知的初始 skill 以通知形式报告且不发送。`runSkillSession` 本身是模块 `v8 ignore` 块内的组装,与 `runTui`/`runMeta` 相同。 +`packages/interaction/tui/tests/tui.spec.ts` 在既有 skill describe 块中新增两个伪终端用例:设置 `config.initialSkill` 时无需用户输入即把渲染后的 skill 正文作为首轮投递;未知的初始 skill 以通知形式报告且不发送。`runSkillSession` 本身是模块 `v8 ignore` 块内的组装,与 `runTui`/`runMeta` 相同。 无 keyless PTY 快照:依据维护者对本次改动的范围裁定,单元覆盖加交互式验证已足够,且播种走的是已有快照的 `/skill:` 渲染路径。两个命令均已在 tmux 中从临时 cwd 交互式验证:`dsh migrate` 加载 `dsh-migrate` 并询问源 agent;`dsh upgrade` 加载 `dsh-upgrade`,后者引入 `dsh-customize` 并开始 checkout 发现。 diff --git a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.i18n.yaml index f51277307f..e400692950 100644 --- a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md -2026-07-07-tool-call-timeout-policy.md: 69fd1ee721de69621d3b57c10d960da0651b94dd -2026-07-07-tool-call-timeout-policy.zh.md: 0b8d07788dce152985e133923bb673f80175e68d +2026-07-07-tool-call-timeout-policy.md: b85a4d2d60ef60d9f75bd47d4d405aa55c132af6 +2026-07-07-tool-call-timeout-policy.zh.md: e67b9e4223f486208061f3944369cf3ec06fe72d diff --git a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md index 69fd1ee721..b85a4d2d60 100644 --- a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md +++ b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md @@ -40,7 +40,7 @@ That the catch is the base `next` — not something outside the waterfall — is ### The `timeout-policy` plugin -The plugin is `@deepseek-ai/dsh-timeout-policy`, a zero-config function/namespace plugin (`name` / `inject` / `apply`) in the `packages/timeout/` group. The per-tool budget is DECLARED on the tool, not on this plugin: a `ToolDefinition` carries an optional `timeoutMs`, which the owning tool plugin sets from its own config. `dsh-tool-web`, for example, resolves `fetchTimeoutMs` / `searchTimeoutMs` (default 30000) onto the `web_fetch` / `web_search` definitions: +The plugin is `@deepseek-ai/dsh-timeout-policy`, a zero-config function/namespace plugin (`name` / `inject` / `apply`) in the `packages/guard/` group (originally its own `timeout/` group). The per-tool budget is DECLARED on the tool, not on this plugin: a `ToolDefinition` carries an optional `timeoutMs`, which the owning tool plugin sets from its own config. `dsh-tool-web`, for example, resolves `fetchTimeoutMs` / `searchTimeoutMs` (default 30000) onto the `web_fetch` / `web_search` definitions: ```yaml - id: timeout-policy @@ -89,7 +89,7 @@ A future model-facing grep/glob tool can be implemented on top of `ctx.bash` wit ## Alternatives considered -**Name the plugin `tool-timeout`.** The literal Agent Note name matched the `gen-tool-catalog` completeness guard's `packages/*/tool-*` glob, which requires every match to register a model-facing tool. This plugin registers none — it is a `tools/execute` wrapper — so a `tool-*` name would either fail `verify-tool-catalog` or force a misleading boot entry. The package is `@deepseek-ai/dsh-timeout-policy` in a new `packages/timeout/` group; the cordis.yml `id` can still be `timeout-policy`. +**Name the plugin `tool-timeout`.** The literal Agent Note name matched the `gen-tool-catalog` completeness guard's `packages/*/tool-*` glob, which requires every match to register a model-facing tool. This plugin registers none — it is a `tools/execute` wrapper — so a `tool-*` name would either fail `verify-tool-catalog` or force a misleading boot entry. The package is `@deepseek-ai/dsh-timeout-policy` in what was then a new `timeout/` group, since folded into `packages/guard/`; the cordis.yml `id` can still be `timeout-policy`. **Keep per-tool timeout handling only.** This was the shape for `bash` and `web_fetch`, and it matches Claude Code and Codex for shell commands. It loses for web-style tools because every new timeout-capable tool must choose validation, cap semantics, docs, snapshots, and classification. The plugin centralizes policy and classification while leaving each tool's schema focused on business input. diff --git a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.zh.md b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.zh.md index 0b8d07788d..e67b9e4223 100644 --- a/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.zh.md @@ -40,7 +40,7 @@ catch 是基础 `next`(而非 waterfall 之外的东西)这一点至关重 ### `timeout-policy` 插件 -该插件是 `@deepseek-ai/dsh-timeout-policy`,一个零配置的函数/命名空间插件(`name` / `inject` / `apply`),位于 `packages/timeout/` 组。每个工具的预算声明在工具自身,而非本插件:`ToolDefinition` 携带一个可选的 `timeoutMs`,由拥有该工具的插件从自身配置中设置。例如 `dsh-tool-web` 将 `fetchTimeoutMs` / `searchTimeoutMs`(默认 30000)解析到 `web_fetch` / `web_search` 的定义上: +该插件是 `@deepseek-ai/dsh-timeout-policy`,一个零配置的函数/命名空间插件(`name` / `inject` / `apply`),位于 `packages/guard/` 组。每个工具的预算声明在工具自身,而非本插件:`ToolDefinition` 携带一个可选的 `timeoutMs`,由拥有该工具的插件从自身配置中设置。例如 `dsh-tool-web` 将 `fetchTimeoutMs` / `searchTimeoutMs`(默认 30000)解析到 `web_fetch` / `web_search` 的定义上: ```yaml - id: timeout-policy @@ -89,7 +89,7 @@ function toolTimeoutResult(timeoutMs: number): ToolExecutionResult { ## 曾考虑的替代方案 -**将插件命名为 `tool-timeout`。** 字面的 Agent Note 名称匹配了 `gen-tool-catalog` 完整性守卫的 `packages/*/tool-*` glob,该 glob 要求每个匹配项注册一个面向模型的工具。本插件不注册任何工具——它是一个 `tools/execute` 包装器——因此 `tool-*` 名称要么导致 `verify-tool-catalog` 失败,要么强制产生一个误导性的启动条目。包为 `@deepseek-ai/dsh-timeout-policy`,位于新的 `packages/timeout/` 组;cordis.yml 的 `id` 仍可为 `timeout-policy`。 +**将插件命名为 `tool-timeout`。** 字面的 Agent Note 名称匹配了 `gen-tool-catalog` 完整性守卫的 `packages/*/tool-*` glob,该 glob 要求每个匹配项注册一个面向模型的工具。本插件不注册任何工具——它是一个 `tools/execute` 包装器——因此 `tool-*` 名称要么导致 `verify-tool-catalog` 失败,要么强制产生一个误导性的启动条目。包为 `@deepseek-ai/dsh-timeout-policy`,位于新的 `packages/guard/` 组;cordis.yml 的 `id` 仍可为 `timeout-policy`。 **仅保留逐工具的超时处理。** 这是 `bash` 和 `web_fetch` 的既有形态,也与 Claude Code 和 Codex 对 shell 命令的做法一致。它对 web 类工具不利,因为每个新的支持超时的工具都必须自行选择校验方式、上限语义、文档、快照和分类。插件集中了策略和分类,让每个工具的 schema 专注于业务输入。 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml index 2f56298d57..82d87580f8 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md -2026-07-19-cooperative-tool-cancellation.md: be237f6ca9475699bb4af76896772a1a7409033d -2026-07-19-cooperative-tool-cancellation.zh.md: dbc74588931a2ae75678b0026df7a8175b0d20b6 +2026-07-19-cooperative-tool-cancellation.md: 4e82452cd6883593d4693766bea803c97fa4c977 +2026-07-19-cooperative-tool-cancellation.zh.md: 1d9fd3dcc8fa65e6f06050be749eec6ac9e996c2 diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md index be237f6ca9..4e82452cd6 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.md @@ -46,7 +46,7 @@ This decision requires cancellation at the tool invocation seam only. Making sig ## Verification -[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) proves the required exact signal types, readonly observer and tool views, mutable-but-required around-dispatch view, and `defineTool()` inference. [`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) covers pre-aborted materialization, phase skipping, policy and wrapper races, body invocation classification, caller-signal fusion, error precedence, context retention, and quiescent drainage. [`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) and [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) cover balanced durable results for undispatched siblings. [`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) and first-party integration suites cover explicit forwarding, while [`timeout-policy.spec.ts`](../../../../packages/timeout/timeout-policy/tests/timeout-policy.spec.ts) preserves timeout ownership. +[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) proves the required exact signal types, readonly observer and tool views, mutable-but-required around-dispatch view, and `defineTool()` inference. [`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) covers pre-aborted materialization, phase skipping, policy and wrapper races, body invocation classification, caller-signal fusion, error precedence, context retention, and quiescent drainage. [`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) and [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) cover balanced durable results for undispatched siblings. [`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) and first-party integration suites cover explicit forwarding, while [`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) preserves timeout ownership. No registry test can prove that arbitrary third-party same-process code observes the signal or stops in bounded time. Capability tests continue to prove cancellation and quiescence at the boundary that owns each side effect. diff --git a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md index dbc7458893..1d9fd3dcc8 100644 --- a/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-19-cooperative-tool-cancellation.zh.md @@ -46,7 +46,7 @@ Status: implemented ## 验证 -[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) 证明必填的精确信号类型、观察者与工具的只读视图、环绕调度可替换但不可删除的视图,以及 `defineTool()` 推断。[`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) 覆盖进入时已中止的物化与阶段跳过、策略和包装层竞态、工具主体调用分类、调用方信号融合、错误优先级、上下文保留和完全停稳。[`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) 与 [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) 覆盖为未调度的同批调用补齐持久化结果。[`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) 和第一方集成测试覆盖显式转发,[`timeout-policy.spec.ts`](../../../../packages/timeout/timeout-policy/tests/timeout-policy.spec.ts) 保持超时归属。 +[`execution-signal-types.spec.ts`](../../../../packages/core/tools/tests/execution-signal-types.spec.ts) 证明必填的精确信号类型、观察者与工具的只读视图、环绕调度可替换但不可删除的视图,以及 `defineTool()` 推断。[`tools.spec.ts`](../../../../packages/core/tools/tests/tools.spec.ts) 覆盖进入时已中止的物化与阶段跳过、策略和包装层竞态、工具主体调用分类、调用方信号融合、错误优先级、上下文保留和完全停稳。[`tool-calls.spec.ts`](../../../../packages/core/agent-loop/tests/tool-calls.spec.ts) 与 [`contract-regressions.spec.ts`](../../../../packages/core/agent-loop/tests/contract-regressions.spec.ts) 覆盖为未调度的同批调用补齐持久化结果。[`code-mode.spec.ts`](../../../../packages/core/tools/tests/code-mode.spec.ts) 和第一方集成测试覆盖显式转发,[`timeout-policy.spec.ts`](../../../../packages/guard/timeout-policy/tests/timeout-policy.spec.ts) 保持超时归属。 任何注册表测试都无法证明任意第三方同进程代码会观察信号或在有界时间内停止。各能力的测试仍需在拥有相应副作用的边界证明取消与完全停稳。 diff --git a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.i18n.yaml index 6c888c7cad..e556108299 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.md -2026-07-30-package-manager-native-repository-cache.md: f8a6706065a936ca4a9abf2a50d266a60f09b252 -2026-07-30-package-manager-native-repository-cache.zh.md: b1fea3d655f8d7aeb466744dc27bbf4ba69993ec +2026-07-30-package-manager-native-repository-cache.md: 0ea145f20a58e4e3e9feb7ba5574818c0352e2c4 +2026-07-30-package-manager-native-repository-cache.zh.md: ac72e5dc4ffa764e1dc430764a6f52fc083d32a4 diff --git a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.md b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.md index f8a6706065..0ea145f20a 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.md +++ b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.md @@ -44,4 +44,4 @@ An identical specifier permanently reuses its published entry. The caller change ## Testing -`packages/ui/app-boot/tests/repository-cache.spec.ts` covers same-process single-flight, cross-instance cache reuse, exact-specifier separation, failed-stage cleanup and retry, and boundary validation. Its real local-Git case invokes the bundled pnpm, runs the fixture repository's `prepare` script, and reads the prepared file from the installed cache entry without network access. +`packages/boot/app-boot/tests/repository-cache.spec.ts` covers same-process single-flight, cross-instance cache reuse, exact-specifier separation, failed-stage cleanup and retry, and boundary validation. Its real local-Git case invokes the bundled pnpm, runs the fixture repository's `prepare` script, and reads the prepared file from the installed cache entry without network access. diff --git a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.zh.md b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.zh.md index b1fea3d655..ac72e5dc4f 100644 --- a/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-30-package-manager-native-repository-cache.zh.md @@ -44,4 +44,4 @@ Loader 将 `pnpm@11.7.0` 作为固定版本的运行时依赖,并使用当前 ## 测试 -`packages/ui/app-boot/tests/repository-cache.spec.ts` 覆盖同进程 single-flight、跨实例缓存复用、精确 specifier 隔离、失败暂存清理与重试,以及边界校验。其真实本地 Git 用例会调用随附的 pnpm,运行 fixture(测试前置数据)仓库的 `prepare` 脚本,并在不访问网络的情况下,从已安装缓存项中读取准备后的文件。 +`packages/boot/app-boot/tests/repository-cache.spec.ts` 覆盖同进程 single-flight、跨实例缓存复用、精确 specifier 隔离、失败暂存清理与重试,以及边界校验。其真实本地 Git 用例会调用随附的 pnpm,运行 fixture(测试前置数据)仓库的 `prepare` 脚本,并在不访问网络的情况下,从已安装缓存项中读取准备后的文件。 diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml index 487726c0e3..105a9d3cfd 100644 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md -2026-07-08-self-referential-cordis-toolset.md: 335d5e808016ebc37c8457c5dcf4d7da9d8b8c93 -2026-07-08-self-referential-cordis-toolset.zh.md: 46d492cd92495a1e4d516db3f82c9e75486bc386 +2026-07-08-self-referential-cordis-toolset.md: 29d331008d4111ae00147f2ddd19a628135339ac +2026-07-08-self-referential-cordis-toolset.zh.md: 55013b774b4b6b371d9cbe6ac492b4e95500a7ff diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md index 335d5e8080..29d331008d 100644 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md +++ b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md @@ -12,7 +12,7 @@ First, model-written registration must be validated where it happens: a malforme ## Decision -The toolset ships as [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) and is demoed by `examples/web-cordis`. It gives the model three tools over the live Cordis runtime in the current DSH process: inspect it, mount an in-memory temporary Plugin, and unmount that Plugin to quiescence. +The toolset ships as [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/self-modification/tool-cordis/README.md) and is demoed by `examples/web-cordis`. It gives the model three tools over the live Cordis runtime in the current DSH process: inspect it, mount an in-memory temporary Plugin, and unmount that Plugin to quiescence. The vm isolates accidental global pollution, and the context façade hides framework internals. Neither restricts the authority of exposed services: a temporary Plugin can call `ctx.bash` with the host executor's privileges and reach the real filesystem and web services. It runs in the shared DSH runtime and may affect other sessions in that process. This is an opt-in development tool with bash-equivalent trust, not a security boundary or product default. diff --git a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md index 46d492cd92..55013b774b 100644 --- a/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md +++ b/.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -该工具集以 [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/cordis/tool-cordis/README.md) 发布,并由 `examples/web-cordis` 演示。它为模型提供三个工具,用于操作当前 DSH 进程中的活跃 Cordis 运行时:检查该运行时、挂载一个仅存于内存的临时 Plugin,再将该 Plugin 卸载至完全停稳。 +该工具集以 [`@deepseek-ai/dsh-tool-cordis`](../../../../packages/self-modification/tool-cordis/README.md) 发布,并由 `examples/web-cordis` 演示。它为模型提供三个工具,用于操作当前 DSH 进程中的活跃 Cordis 运行时:检查该运行时、挂载一个仅存于内存的临时 Plugin,再将该 Plugin 卸载至完全停稳。 vm 隔离了意外的全局污染,上下文门面隐藏了框架内部细节。但二者都不限制已暴露服务的权限:临时 Plugin 可以调用 `ctx.bash` 以宿主执行器的权限运行命令,也能访问真实的文件系统和网络服务。它运行在共享 DSH runtime 中,可能影响同一进程的其他 session。这是一个需要显式启用的开发工具,信任等级与 bash 相当,不是安全边界,也不是产品默认配置。 diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml index cbe53b9622..d1cef43cc1 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md -2026-07-31-even-out-shipped-tool-rosters.md: d12db993654d9f2b41a16a4663dc64aefe8e3a2f -2026-07-31-even-out-shipped-tool-rosters.zh.md: 8381a457def1a909b300de52dc011e2437c7e9f3 +2026-07-31-even-out-shipped-tool-rosters.md: 0195620055da5e570d2f54792d950a88bab8d652 +2026-07-31-even-out-shipped-tool-rosters.zh.md: cc67f24720f650283daa4ee85a0a1af5358756a8 diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md index d12db99365..0195620055 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.md @@ -22,7 +22,7 @@ Two rows stay surface-specific. `tmux-context` is TUI-only because a browser sur Three capabilities stay out on the evidence their own packages record, and are listed here so "we forgot" and "we decided against" stay distinguishable. -**`dsh-tool-cordis`** lets the model write JavaScript and mount it as a temporary plugin. Its README states the limit: "The sandbox is containment for honest code, not a security boundary — host-realm helpers on the sandbox global are reachable, so mount code can reach Node" ([Known limitations](../../../../packages/cordis/tool-cordis/README.md)). The `node:vm` realm lives inside the harness process while `dsh-sandbox-local` confines only the argv it spawns, so on the Web surface both the sandbox and the approval seam are bypassed rather than enforced. +**`dsh-tool-cordis`** lets the model write JavaScript and mount it as a temporary plugin. Its README states the limit: "The sandbox is containment for honest code, not a security boundary — host-realm helpers on the sandbox global are reachable, so mount code can reach Node" ([Known limitations](../../../../packages/self-modification/tool-cordis/README.md)). The `node:vm` realm lives inside the harness process while `dsh-sandbox-local` confines only the argv it spawns, so on the Web surface both the sandbox and the approval seam are bypassed rather than enforced. **`dsh-web-fetch-local`** stays unmounted and `dsh-tool-web` keeps `fetch: false`. SSRF protection is deferred in the implementation ([`policy.ts`](../../../../packages/web/web-fetch-local/src/policy.ts) validates protocol, credentials, and length only) and the package says so: "this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets" ([README](../../../../packages/web/web-fetch-local/README.md)). The model chooses the target, which includes the harness's own gateway on loopback, private ranges, and cloud metadata endpoints. diff --git a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md index 8381a457de..cc67f24720 100644 --- a/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-even-out-shipped-tool-rosters.zh.md @@ -22,7 +22,7 @@ Status: implemented 有三项能力基于其自身包所记录的证据保持在外,列在这里是为了让「我们忘了」和「我们决定不要」保持可区分。 -**`dsh-tool-cordis`** 让模型写一段 JavaScript 并挂成临时插件。它的 README 写明了这个界限:「The sandbox is containment for honest code, not a security boundary — host-realm helpers on the sandbox global are reachable, so mount code can reach Node」([Known limitations](../../../../packages/cordis/tool-cordis/README.md))。`node:vm` 的 realm 就在 harness 进程内,而 `dsh-sandbox-local` 只约束它 spawn 出去的 argv,因此在 Web surface 上,沙箱与批准接缝是被绕过而非被执行。 +**`dsh-tool-cordis`** 让模型写一段 JavaScript 并挂成临时插件。它的 README 写明了这个界限:「The sandbox is containment for honest code, not a security boundary — host-realm helpers on the sandbox global are reachable, so mount code can reach Node」([Known limitations](../../../../packages/self-modification/tool-cordis/README.md))。`node:vm` 的 realm 就在 harness 进程内,而 `dsh-sandbox-local` 只约束它 spawn 出去的 argv,因此在 Web surface 上,沙箱与批准接缝是被绕过而非被执行。 **`dsh-web-fetch-local`** 保持不挂,`dsh-tool-web` 保持 `fetch: false`。SSRF 防护在实现中是 deferred 状态([`policy.ts`](../../../../packages/web/web-fetch-local/src/policy.ts) 只校验协议、凭据与长度),包里也直说了:「this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets」([README](../../../../packages/web/web-fetch-local/README.md))。目标由模型选择,其中包括 harness 自己跑在环回地址上的网关、内网段和云元数据端点。 diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml index ecfb0ca0e8..4396748bc7 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md -2026-07-29-shared-base-config-overlays.md: 9fae9775e0b37acc99ce2eb5fc630e6f4f52bf1d -2026-07-29-shared-base-config-overlays.zh.md: 20cc3e522c369315a1fafc84778fced0260581d0 +2026-07-29-shared-base-config-overlays.md: b2ca5cc8d3a160dd09550827d5f8148c14048ea4 +2026-07-29-shared-base-config-overlays.zh.md: e14aa87bdd6ed3bd711bb9786220b5baf0566e8e diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md index 9fae9775e0..b2ca5cc8d3 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.md @@ -24,7 +24,7 @@ Precedence is list order, last write winning per row: base, then the surface ove A patch replaces its target row's whole `config` rather than merging, which shapes the split: a row whose value differs per surface lives in the overlays, never in the base, so no row is patched by three layers at once. Session identity therefore cannot ride a config key at all — it moved to `dsh-agent-loop`'s `CONFIGURED_AGENT_IDENTITIES_KEY`, as the launcher-owned identity record documented. -`examples/tui-agent`, `examples/cordis-agent`, `examples/code-mode`, and `packages/examples/tui-demo` are deleted. The TUI tests move to `apps/cli/tests/`, the cordis-toolset e2e to `packages/cordis/tool-cordis/tests/`, and the supported Code Mode demo remains the ACP overlay at `examples/acp-agent/code-mode.cordis.yml`. +`examples/tui-agent`, `examples/cordis-agent`, `examples/code-mode`, and `packages/examples/tui-demo` are deleted. The TUI tests move to `apps/cli/tests/`, the cordis-toolset e2e to `packages/self-modification/tool-cordis/tests/`, and the supported Code Mode demo remains the ACP overlay at `examples/acp-agent/code-mode.cordis.yml`. ## Alternatives considered @@ -48,6 +48,6 @@ A patch whose `id` matches no row stays a no-op rather than an error. That is de Composition is checked by booting each tree through the real Loader and inspecting settled entries, not by reading YAML; both surfaces settle with zero unloaded rows, and Web starts its `httpServer` with sandboxed Bash and filesystem providers. Code Mode remains covered by the ACP overlay and programmatic TUI snapshots rather than a separate shipped TUI application. -All eight terminal snapshot scenarios replay byte-identically after moving, and the 14-case PTY smoke passes, including two cases that assert a personal overlay reaches an **inserted** row — the behavior the vendored `plugin-include` fix enables ([`vendor/README.md`](../../../../vendor/README.md) local modification 8, covered by `packages/ui/app-boot/tests/config-reload.spec.ts`). +All eight terminal snapshot scenarios replay byte-identically after moving, and the 14-case PTY smoke passes, including two cases that assert a personal overlay reaches an **inserted** row — the behavior the vendored `plugin-include` fix enables ([`vendor/README.md`](../../../../vendor/README.md) local modification 8, covered by `packages/boot/app-boot/tests/config-reload.spec.ts`). Flattening surfaced three latent defects, each fixed here: the TUI captured the optional `sessionQuery` service once at construction and so could permanently disable `/resume` when it won the mount race; the shipped session-store root silently reverted to a project-local `./.sessions`; and `--config-replace` was dropped by the resume handoff. diff --git a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md index 20cc3e522c..e14aa87bdd 100644 --- a/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md +++ b/.agents/notes/implemented/simplification/2026-07-29-shared-base-config-overlays.zh.md @@ -24,7 +24,7 @@ Status: implemented patch 会整体替换目标配置项的 `config` 而不合并,这决定了拆分方式:取值因 surface 而异的配置项住在 overlay 中,绝不住在 base 里,从而没有任何配置项会被三层同时 patch。因此会话身份根本不能经由配置键传递——它迁移到了 `dsh-agent-loop` 的 `CONFIGURED_AGENT_IDENTITIES_KEY`,正如启动器持有身份的记录所述。 -`examples/tui-agent`、`examples/cordis-agent`、`examples/code-mode` 与 `packages/examples/tui-demo` 均被删除。TUI 测试迁往 `apps/cli/tests/`,cordis 工具集的 e2e 迁入 `packages/cordis/tool-cordis/tests/`,受支持的 Code Mode demo 则保留为 `examples/acp-agent/code-mode.cordis.yml` 中的 ACP overlay。 +`examples/tui-agent`、`examples/cordis-agent`、`examples/code-mode` 与 `packages/examples/tui-demo` 均被删除。TUI 测试迁往 `apps/cli/tests/`,cordis 工具集的 e2e 迁入 `packages/self-modification/tool-cordis/tests/`,受支持的 Code Mode demo 则保留为 `examples/acp-agent/code-mode.cordis.yml` 中的 ACP overlay。 ## 备选方案 @@ -48,6 +48,6 @@ patch 会整体替换目标配置项的 `config` 而不合并,这决定了拆 组合的正确性通过用真实 Loader 启动每棵树并检查已就绪的条目来核对,而不是靠阅读 YAML:两个界面都能稳定完成且没有未加载项;Web 会以沙箱化 Bash 与文件系统提供方启动 `httpServer`。Code Mode 继续由 ACP overlay 与程序化 TUI 快照覆盖,而不再维护独立交付的 TUI 应用。 -全部八个终端快照场景在迁移后逐字节重放一致,14 个用例的 PTY 冒烟测试全部通过,其中两个用例断言个人 overlay 能触达一个 **insert 进来的**配置项——这正是 vendored `plugin-include` 修复所启用的行为([`vendor/README.md`](../../../../vendor/README.md) 本地修改第 8 条,由 `packages/ui/app-boot/tests/config-reload.spec.ts` 覆盖)。 +全部八个终端快照场景在迁移后逐字节重放一致,14 个用例的 PTY 冒烟测试全部通过,其中两个用例断言个人 overlay 能触达一个 **insert 进来的**配置项——这正是 vendored `plugin-include` 修复所启用的行为([`vendor/README.md`](../../../../vendor/README.md) 本地修改第 8 条,由 `packages/boot/app-boot/tests/config-reload.spec.ts` 覆盖)。 平铺过程暴露出三处潜伏缺陷,均在此一并修复:TUI 曾在构造时一次性捕获可选的 `sessionQuery` 服务,因此在挂载竞争中胜出时会永久禁用 `/resume`;交付的会话存储根目录曾静默退回项目本地的 `./.sessions`;`--config-replace` 曾在 resume 交接中被丢弃。 diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.i18n.yaml b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.i18n.yaml index b7cfbde315..af8cad7d63 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.i18n.yaml +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md -2026-07-04-prune-dead-core-spine-surface.md: 473f655ab0944b43f9b1193413801eb7a80286d8 -2026-07-04-prune-dead-core-spine-surface.zh.md: 3389c0161efa78252c9810d64f46d0cd8a7a034c +2026-07-04-prune-dead-core-spine-surface.md: 7ee7ffc6e8c0f3672c98be6e23ef45a1abfc2b4e +2026-07-04-prune-dead-core-spine-surface.zh.md: b65d607c1736545089c7116f8a14ab6e7510a8e1 diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md index 473f655ab0..7ee7ffc6e8 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md @@ -8,7 +8,7 @@ English | [中文](2026-07-04-prune-dead-core-spine-surface.zh.md) Several package-root exports, result fields, and convenience methods have no production consumer. They survive because tests import internals through public entry points or because a type anticipated a caller that never arrived. Each item is small in isolation, but together they enlarge the SDK contract, generated catalogs, documentation, and regression matrix without enabling a shipped path. -The production corpus is `packages/*/*/src`, example sources/config, and runtime scripts. Tests, package READMEs, and Agent Note prose are evidence of publication but not fixed callers. `cordis_inspect` makes `packages/cordis/tool-cordis/src/api-catalog.ts` model-visible, and `cordis_mount` can invoke injected services through guarded real-service proxies, so catalogued service methods and returned shapes are a genuine dynamic product surface. The table therefore distinguishes absence of a fixed repository caller from unreachability: rows touching catalogued vocabulary intentionally contract what model-written mounts can discover and call, while package-root implementation helpers are not reached through that service façade. Exact-symbol searches produce the following inventory: +The production corpus is `packages/*/*/src`, example sources/config, and runtime scripts. Tests, package READMEs, and Agent Note prose are evidence of publication but not fixed callers. `cordis_inspect` makes `packages/self-modification/tool-cordis/src/api-catalog.ts` model-visible, and `cordis_mount` can invoke injected services through guarded real-service proxies, so catalogued service methods and returned shapes are a genuine dynamic product surface. The table therefore distinguishes absence of a fixed repository caller from unreachability: rows touching catalogued vocabulary intentionally contract what model-written mounts can discover and call, while package-root implementation helpers are not reached through that service façade. Exact-symbol searches produce the following inventory: | Surface | Production evidence | Simplification | | --- | --- | --- | diff --git a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.zh.md b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.zh.md index 3389c0161e..b65d607c17 100644 --- a/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.zh.md +++ b/.agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.zh.md @@ -8,7 +8,7 @@ Status: proposed 若干包根导出、结果字段和便利方法没有生产消费方。它们之所以存活,要么是因为测试通过公开入口导入了内部实现,要么是因为某个类型预期了一个从未出现的调用者。每一项单独看都很小,但合在一起,它们扩大了 SDK 契约、生成的 catalog、文档和回归矩阵,却没有支撑任何已交付的路径。 -生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Note 行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/cordis/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单: +生产语料库是 `packages/*/*/src`、示例源码/配置和运行时脚本。测试、包 README 和 Agent Note 行文是发布的证据,但不是固定调用者。`cordis_inspect` 使 `packages/self-modification/tool-cordis/src/api-catalog.ts` 对模型可见,`cordis_mount` 可以通过受保护的真实服务代理调用注入的服务,因此 catalog 中的服务方法和返回形状是真正的动态产品接口。下表因此区分「没有固定的仓库调用者」与「不可达」:涉及 catalog 词汇的行有意收缩模型编写的 mount 能发现和调用的内容,而包根实现辅助函数并不通过该服务门面可达。精确符号搜索得出以下清单: | 接口 | 生产证据 | 简化方式 | | --- | --- | --- | diff --git a/AGENTS.md b/AGENTS.md index 7c9b795ddc..35fbc54ca6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,9 +29,9 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// bundle/ profile plugin bundles: installable patch layers for dsh --profile workflow/ workflow seam + worker-thread engine + workflow tool todo/ todo_write tool - plan/ plan mode as logged per-agent collaboration state - guard/ loop-hygiene plugins - cordis/ self-referential toolset: the agent inspects/mounts plugins in its own runtime + plan/ plan mode as logged collaboration state + guard/ loop-hygiene plugins + tool-call timeouts + self-modification/ the agent inspects/mounts plugins in its runtime hooks/ Claude Code/Codex hook bridges + shared wire-protocol library session/ durable session data plane: persistence, projection, titles, reporting settings/ user-settings seam + file-backed provider diff --git a/README.i18n.yaml b/README.i18n.yaml index c4ad1f0fd9..077f5fa7f2 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write README.md -README.md: 64f06c0aec0905fa7deabbec0deea61e1c7a40d4 -README.zh.md: fee03118926028833c828809764ebb5f6375259e +README.md: ff4cf661a060772973629844d43758d26ac1be02 +README.zh.md: 977de7f22244ff49579393edcce66d02c5782de4 diff --git a/README.md b/README.md index 64f06c0aec..ff4cf661a0 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Built-in capabilities cover file reading, editing, and search; shell and persist - **Everything is a plugin.** Models, tools, policies, storage, context management, and interfaces are composable [Cordis plugins](docs/user/develop/basic/index.md), so deployments can extend or replace behavior without forking the agent loop. See the [architecture](docs/architecture.md) for the underlying design. - **Runs are reconstructable.** Anything visible to the model is logged in the authoritative session stream; persistence, resume/fork/query, replay, telemetry, and UIs derive from the same events. See the [session-log architecture](docs/architecture.md#session-log). - **Code Mode (opt-in).** It exposes a `run_code` tool and a generated TypeScript SDK; only program output re-enters model context. See [Code Mode](packages/core/tools/README.md#code-mode). -- **Self-referential Cordis tools are opt-in.** They let the agent inspect its live runtime and mount or unmount plugins while it runs. See the [Cordis tools](packages/cordis/tool-cordis/README.md). +- **Self-referential Cordis tools are opt-in.** They let the agent inspect its live runtime and mount or unmount plugins while it runs. See the [Cordis tools](packages/self-modification/tool-cordis/README.md). ## Community diff --git a/README.zh.md b/README.zh.md index fee0311892..977de7f222 100644 --- a/README.zh.md +++ b/README.zh.md @@ -76,7 +76,7 @@ pnpm run demo:acp - **一切皆插件。** 模型、工具、策略、存储、上下文管理和界面均可组合为 [Cordis 插件](docs/user/develop/basic/index.md),部署方无需 fork agent loop(智能体循环)即可扩展或替换行为。底层设计见[架构文档](docs/architecture.md)。 - **运行可重建。** 凡是模型可见的内容,都会记录在权威会话流中;持久化、恢复/fork/查询、回放、遥测和 UI 均从同一组事件派生。参见[会话日志架构](docs/architecture.md#session-log)。 - **Code Mode(需显式启用)。** 它会提供 `run_code` 工具和生成的 TypeScript SDK,只有程序输出会重新进入模型上下文。参见 [Code Mode](packages/core/tools/README.md#code-mode)。 -- **自指 Cordis 工具需显式启用。** 这些工具可让 agent 检查自身的实时运行时,并在运行中挂载或卸载插件。参见 [Cordis 工具](packages/cordis/tool-cordis/README.md)。 +- **自指 Cordis 工具需显式启用。** 这些工具可让 agent 检查自身的实时运行时,并在运行中挂载或卸载插件。参见 [Cordis 工具](packages/self-modification/tool-cordis/README.md)。 ## 社区 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index c191f6a659..4b46a6566b 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -389,7 +389,7 @@ flowchart LR | `ctx.sessionReferences` | `core` | [`session-reference`](../packages/context/session-reference) | - | - | - | Projects bounded current-surface conversation snapshots into durable untrusted message context; host adapters own mention syntax. | | `ctx.sessionTitle` | `seam` | [`session-title`](../packages/session/session-title) | [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm), [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | - | - | Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration. | | `ctx.systemPrompt` | `core` | [`system-prompt`](../packages/core/system-prompt) | - | [`agent-loop`](../packages/core/agent-loop), [`tools`](../packages/core/tools), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-web`](../packages/web/tool-web) | - | Collects prompt sections and model-facing tool schemas for each step. | -| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/cordis/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | +| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/bash/tool-bash), [`tool-cordis`](../packages/self-modification/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-pty`](../packages/pty/tool-pty), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | Registers capabilities, owns Code Mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. | | `ctx.userInteraction` | `seam` | [`user-interaction`](../packages/interaction/user-interaction) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise. | | `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | Folds logged plan/mode state, flushes user selections at turn boundaries, renders deployment-owned guidance, registers /plan, and keeps the plan-exit schema stable across transitions. | | `ctx.commands` | `core` | [`commands`](../packages/interaction/commands) | - | - | - | Plugins register direct human commands without sending invocations to the model. | diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 0cb985d07d..699617eef4 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -280,7 +280,7 @@ Inspect the live Cordis runtime in the current DSH process. Read-only. Sections: } ``` -Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts) +Source: [`packages/self-modification/tool-cordis/src/index.ts`](../packages/self-modification/tool-cordis/src/index.ts) ### `cordis_mount` @@ -301,7 +301,7 @@ Mount a temporary Cordis Plugin in the current DSH process. This creates an in-m } ``` -Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts) +Source: [`packages/self-modification/tool-cordis/src/index.ts`](../packages/self-modification/tool-cordis/src/index.ts) ### `cordis_unmount` @@ -322,7 +322,7 @@ Unmount a current-process temporary Plugin created by cordis_mount. Waits for it } ``` -Source: [`packages/cordis/tool-cordis/src/index.ts`](../packages/cordis/tool-cordis/src/index.ts) +Source: [`packages/self-modification/tool-cordis/src/index.ts`](../packages/self-modification/tool-cordis/src/index.ts) Not in any shipped tree (a deliberate opt-in — temporary Plugin code reaches the real runtime, see .agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). Plugins created by cordis_mount may register ADDITIONAL model-visible tools until unmounted or DSH restarts; a full changed request header logs those tool-set changes. diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 68da8ae611..039e8bb78d 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: 198caca8c8b8231ece4d7160c809ba7b881e775c -README.zh.md: bf2bf437796d904d135b610ff2c7a2d4d21b0298 +README.md: 7589f37b174b94558bff9f74f860bb4b42705fb8 +README.zh.md: 4e99dd9313cca89e84ae0bfaf4a2179d726da864 diff --git a/packages/README.md b/packages/README.md index 198caca8c8..7589f37b17 100644 --- a/packages/README.md +++ b/packages/README.md @@ -34,10 +34,9 @@ Groups hold `packages///`; names stay `@deepseek-ai/dsh-`. **Gr | [`spill/`](spill/README.md) | Spill capability family: storage seam, local impl, tool-result spill policy | Product — stable surface | | [`todo/`](todo/README.md) | The model-facing `todo_write` tool | Product — stable surface | | [`plan/`](plan/README.md) | Plan collaboration state with a direct entry command and reviewed exit | Product — stable surface | -| [`timeout/`](timeout/README.md) | Tool-call `tools/execute` deadline enforcement | Product — stable surface | -| [`guard/`](guard/README.md) | Loop-hygiene advisory repeat-call reminders | Product — stable surface | +| [`guard/`](guard/README.md) | Loop-hygiene guards: advisory repeat-call reminders + the `tools/execute` deadline enforcer | Product — stable surface | | [`bundle/`](bundle/README.md) | Installable `dsh --profile` patch layers | Product — stable surface | -| [`cordis/`](cordis/README.md) | Cordis runtime integration: self-inspection, temporary Plugins, restricted repository Plugin loading | Product — stable surface | +| [`self-modification/`](self-modification/README.md) | The agent modifies its own runtime: inspect the live runtime's plugins and services, mount/unmount model-written plugins ([design](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)) and restricted repository Plugin loading | Product — stable surface | | [`hooks/`](hooks/README.md) | Hook bridges + the shared Claude Code / Codex wire-protocol library | Product — stable surface | | [`session/`](session/README.md) | Durable session data plane: persistence seam + JSONL/SQLite backends, projection seam, log-backed titles, session reporting | Product — stable surface | | [`session-query/`](session-query/README.md) | Session retrieval family: logical corpus, bounded reads, lineage, event relationships, semantic filtering, and SQLite full-text search | Product — stable surface | diff --git a/packages/README.zh.md b/packages/README.zh.md index bf2bf43779..4e99dd9313 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -34,10 +34,9 @@ | [`spill/`](spill/README.md) | 溢出能力系列:存储 seam、本地实现、工具结果溢出策略 | 产品:稳定表面 | | [`todo/`](todo/README.md) | 面向模型的 `todo_write` 工具 | 产品:稳定表面 | | [`plan/`](plan/README.md) | Plan 协作状态,提供直接进入命令与经评审的退出 | 产品:稳定表面 | -| [`timeout/`](timeout/README.md) | 工具调用 `tools/execute` 截止时间强制执行 | 产品:稳定表面 | -| [`guard/`](guard/README.md) | 循环卫生建议性重复调用提醒 | 产品:稳定表面 | +| [`guard/`](guard/README.md) | 循环卫生守卫:建议性重复调用提醒 + `tools/execute` 截止时间强制执行器 | 产品:稳定表面 | | [`bundle/`](bundle/README.md) | 可安装的 `dsh --profile` 补丁层 | 产品:稳定表面 | -| [`cordis/`](cordis/README.md) | Cordis 运行时集成:自检、临时 Plugin、受限 repository Plugin 加载 | 产品:稳定表面 | +| [`self-modification/`](self-modification/README.md) | agent 修改自身运行时:检查实时运行时的插件与服务,挂载/卸载模型所写插件([设计](../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)),以及受限 repository Plugin 加载 | 产品:稳定表面 | | [`hooks/`](hooks/README.md) | 钩子桥接 + 共享 Claude Code/Codex 协议格式库 | 产品:稳定表面 | | [`session/`](session/README.md) | 持久会话数据平面:持久化 seam + JSONL/SQLite 后端、投影 seam、日志支持的标题、会话上报 | 产品:稳定表面 | | [`session-query/`](session-query/README.md) | 会话检索系列:逻辑语料库、有界读取、血缘、事件关系、语义过滤和 SQLite 全文搜索 | 产品:稳定表面 | diff --git a/packages/boot/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts index 5f90a5ee30..dec77aecab 100644 --- a/packages/boot/app-boot/src/index.ts +++ b/packages/boot/app-boot/src/index.ts @@ -641,7 +641,7 @@ export function assertEntriesLoaded(ctx: Context, binName: string): void { /** * Value mirrors used because Cordis's const enum has no runtime object to import. - * Keep aligned with `packages/cordis/tool-cordis/src/fiber-state.ts` and + * Keep aligned with `packages/self-modification/tool-cordis/src/fiber-state.ts` and * `packages/client/web/src/loader-status.ts`. */ const FIBER_PENDING = 0 as FiberState.PENDING diff --git a/packages/cordis/README.md b/packages/cordis/README.md deleted file mode 100644 index e8b0790f91..0000000000 --- a/packages/cordis/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# packages/cordis — Cordis runtime integration - -English | [中文](README.zh.md) - -Plugins that integrate Harness-owned formats with the Cordis runtime: the self-referential model toolset and the restricted repository Plugin runtime. - -| Package | Role | ctx key | -|---|---|---| -| [`tool-cordis/`](tool-cordis/README.md) | Model-facing runtime inspection and temporary-plugin tools | registers on `ctx.tools` | -| [`repository-plugin/`](repository-plugin/README.md) | Repository skill and MCP composition | registers a Loader builtin | diff --git a/packages/cordis/README.zh.md b/packages/cordis/README.zh.md deleted file mode 100644 index c452f9a7ef..0000000000 --- a/packages/cordis/README.zh.md +++ /dev/null @@ -1,10 +0,0 @@ -# packages/cordis — Cordis 运行时集成 - -[English](README.md) | 中文 - -把 Harness 所有的格式与 Cordis 运行时集成的插件:自指的面向模型工具集,以及受限的 repository Plugin 运行时。 - -| 包 | 职责 | ctx key | -|---|---|---| -| [`tool-cordis/`](tool-cordis/README.md) | 面向模型的运行时检查和临时插件工具 | 注册到 `ctx.tools` | -| [`repository-plugin/`](repository-plugin/README.md) | repository skill 与 MCP 组合 | 注册一个 Loader builtin | diff --git a/packages/guard/README.i18n.yaml b/packages/guard/README.i18n.yaml index 637a36063e..8251a76ed5 100644 --- a/packages/guard/README.i18n.yaml +++ b/packages/guard/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/guard/README.md -README.md: 8da2f2ddcc1432e7963e600b35549f5e26121af2 -README.zh.md: aa8b5bd1f7d10d90d1dc84d07a7aad81bfb4259c +README.md: 5791474a2fd6aeda728d4c335c930069ade6e023 +README.zh.md: 82d779d2f1834317672b8fb03f2a4a875bb566fc diff --git a/packages/guard/README.md b/packages/guard/README.md index 8da2f2ddcc..5791474a2f 100644 --- a/packages/guard/README.md +++ b/packages/guard/README.md @@ -2,8 +2,9 @@ English | [中文](README.zh.md) -Behavioral guard plugins watch the agent loop for unproductive patterns and nudge the model back on course. A guard is a self-contained consumer of core seams, not a swappable capability. +Behavioral guard plugins watch the agent loop for unproductive patterns and enforce per-call budgets. A guard is a self-contained consumer of core seams, not a swappable capability. | Package | Role | ctx key | |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | Advisory reminders for repeated tool calls | listens on tool and agent events | +| [`timeout-policy/`](timeout-policy/README.md) | Arms per-call tool deadlines as deployment policy | registers a `tools/execute` listener | diff --git a/packages/guard/README.zh.md b/packages/guard/README.zh.md index aa8b5bd1f7..82d779d2f1 100644 --- a/packages/guard/README.zh.md +++ b/packages/guard/README.zh.md @@ -2,8 +2,9 @@ [English](README.md) | 中文 -行为 guard 插件监视 agent loop(智能体循环)中的无效模式,并推动模型回到正轨。guard 是 core seam 的自包含消费方,而非可替换能力。 +行为 guard 插件监视 agent loop(智能体循环)中的无效模式,并强制执行单次调用预算。guard 是 core seam 的自包含消费方,而非可替换能力。 | 包 | 职责 | ctx key | |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | 针对重复工具调用的建议性提醒 | 监听工具和 agent 事件 | +| [`timeout-policy/`](timeout-policy/README.md) | 以部署策略形式设置单次工具调用截止时间 | 注册 `tools/execute` 监听器 | diff --git a/packages/timeout/timeout-policy/README.i18n.yaml b/packages/guard/timeout-policy/README.i18n.yaml similarity index 79% rename from packages/timeout/timeout-policy/README.i18n.yaml rename to packages/guard/timeout-policy/README.i18n.yaml index 377d2c528d..3c41810118 100644 --- a/packages/timeout/timeout-policy/README.i18n.yaml +++ b/packages/guard/timeout-policy/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/timeout/timeout-policy/README.md +# pnpm run verify-translation-pairing --write packages/guard/timeout-policy/README.md README.md: 3e5769e2f8b95392e9d489659c6bae634ab3ff18 README.zh.md: bfba62cdfae8bb956aec43bcf6e8df12407d8ba2 diff --git a/packages/timeout/timeout-policy/README.md b/packages/guard/timeout-policy/README.md similarity index 100% rename from packages/timeout/timeout-policy/README.md rename to packages/guard/timeout-policy/README.md diff --git a/packages/timeout/timeout-policy/README.zh.md b/packages/guard/timeout-policy/README.zh.md similarity index 100% rename from packages/timeout/timeout-policy/README.zh.md rename to packages/guard/timeout-policy/README.zh.md diff --git a/packages/timeout/timeout-policy/package.json b/packages/guard/timeout-policy/package.json similarity index 100% rename from packages/timeout/timeout-policy/package.json rename to packages/guard/timeout-policy/package.json diff --git a/packages/timeout/timeout-policy/src/index.ts b/packages/guard/timeout-policy/src/index.ts similarity index 91% rename from packages/timeout/timeout-policy/src/index.ts rename to packages/guard/timeout-policy/src/index.ts index 61470ea6af..1c18fa5e13 100644 --- a/packages/timeout/timeout-policy/src/index.ts +++ b/packages/guard/timeout-policy/src/index.ts @@ -2,6 +2,12 @@ * Cooperative tool-call timeout enforcer. A tool declares `timeoutMs` and * promises to honor `exec.signal`; this wrapper arms that deadline and maps its * own expiry to `TOOL_TIMEOUT` without racing or abandoning the tool promise. + * + * FIXME: settle the intended `@deepseek-ai/dsh-timeout-guard` rename before the + * first tagged release — suggestion only, aligning the name with its `guard/` + * home; decide at resolution time + * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * * @module @deepseek-ai/dsh-timeout-policy */ diff --git a/packages/timeout/timeout-policy/src/invariant.ts b/packages/guard/timeout-policy/src/invariant.ts similarity index 100% rename from packages/timeout/timeout-policy/src/invariant.ts rename to packages/guard/timeout-policy/src/invariant.ts diff --git a/packages/timeout/timeout-policy/tests/timeout-policy.spec.ts b/packages/guard/timeout-policy/tests/timeout-policy.spec.ts similarity index 100% rename from packages/timeout/timeout-policy/tests/timeout-policy.spec.ts rename to packages/guard/timeout-policy/tests/timeout-policy.spec.ts diff --git a/packages/timeout/timeout-policy/tsconfig.json b/packages/guard/timeout-policy/tsconfig.json similarity index 100% rename from packages/timeout/timeout-policy/tsconfig.json rename to packages/guard/timeout-policy/tsconfig.json diff --git a/packages/interaction/permission/README.i18n.yaml b/packages/interaction/permission/README.i18n.yaml index b5883174dc..34533f9da4 100644 --- a/packages/interaction/permission/README.i18n.yaml +++ b/packages/interaction/permission/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/permission/README.md -README.md: 814085ed6f2c9650854f377e1c97e442fc4211a4 -README.zh.md: 36880d6b8c3f0b39b88db1abb02534f30e3355fa +README.md: 4f7f560bb81eaad3b6b95b2742432fa252682d5a +README.zh.md: 79d0ce9c095d3426f3219f04d9cb7ec3b161a184 diff --git a/packages/interaction/user-interaction/README.i18n.yaml b/packages/interaction/user-interaction/README.i18n.yaml index f467aaf4e1..feab1813a7 100644 --- a/packages/interaction/user-interaction/README.i18n.yaml +++ b/packages/interaction/user-interaction/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/user-interaction/README.md -README.md: d234d6677bdd772f1bbd2c979c0d41f90aef5c32 -README.zh.md: c89210b6955a661313ca9e0e82e43da5a4d1db79 +README.md: d62e75d110b8be339c5f9449b0834320f695ac99 +README.zh.md: 55258e85e56df2375ed8f195fa0b3b731a9cb816 diff --git a/packages/scaffold/protocol/README.i18n.yaml b/packages/scaffold/protocol/README.i18n.yaml index df0623573b..b77c8fce63 100644 --- a/packages/scaffold/protocol/README.i18n.yaml +++ b/packages/scaffold/protocol/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/protocol/README.md -README.md: dc42f385f6e7fd6327ca79887d3cf22d7c34ccdf -README.zh.md: a292120be8b5b09aaadb05f9dbc272a51bd74831 +README.md: 35b62d2ffc43c54dc81f6a5ecfb1a0ef4294439d +README.zh.md: a02ba94e7d8fae19bff48d8fda363e23fc173850 diff --git a/packages/scaffold/server/README.i18n.yaml b/packages/scaffold/server/README.i18n.yaml index 0672ad22a6..46a18ec199 100644 --- a/packages/scaffold/server/README.i18n.yaml +++ b/packages/scaffold/server/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/server/README.md -README.md: 7ada4fbe6a53e90fcd45c20d9472cb87dbf6b4cf -README.zh.md: b8362f7762b963ed6caff1b2ba87421152ecb483 +README.md: e0e06e27320ab41f131a1a36a7ccbd26271ce67c +README.zh.md: ed87a869180571a3cbb51c2ce92a0647e545ca3a diff --git a/packages/cordis/README.i18n.yaml b/packages/self-modification/README.i18n.yaml similarity index 55% rename from packages/cordis/README.i18n.yaml rename to packages/self-modification/README.i18n.yaml index 2eacddb52c..9a6a070b95 100644 --- a/packages/cordis/README.i18n.yaml +++ b/packages/self-modification/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/cordis/README.md -README.md: e8b0790f911d1acc1216d412191ca967e0e1ac27 -README.zh.md: c452f9a7ef69e7f5af8900153271d500f19f9e47 +# pnpm run verify-translation-pairing --write packages/self-modification/README.md +README.md: 53f700993cce8df8729fba5a92a9498fff2412cd +README.zh.md: 9874ead263e9860b94992cb176be3c6f8c454ece diff --git a/packages/self-modification/README.md b/packages/self-modification/README.md new file mode 100644 index 0000000000..53f700993c --- /dev/null +++ b/packages/self-modification/README.md @@ -0,0 +1,10 @@ +# self-modification/ — the agent modifies its own runtime + +English | [中文](README.zh.md) + +Model-facing tools over the live cordis runtime the agent itself runs inside: inspect the loaded plugins and service surface, mount model-written plugins, and dispose them again — plus the restricted repository Plugin runtime. The group is the landing zone for future self-modification packages. Design home: [the toolset Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). + +| Package | Role | ctx key | +|---|---|---| +| [`tool-cordis/`](tool-cordis/README.md) | Model-facing runtime inspection and temporary-plugin tools | registers on `ctx.tools` | +| [`repository-plugin/`](repository-plugin/README.md) | Repository skill and MCP composition | registers a Loader builtin | diff --git a/packages/self-modification/README.zh.md b/packages/self-modification/README.zh.md new file mode 100644 index 0000000000..9874ead263 --- /dev/null +++ b/packages/self-modification/README.zh.md @@ -0,0 +1,10 @@ +# self-modification/:agent 修改自身运行时 + +[English](README.md) | 中文 + +这些面向模型的工具作用于 agent(智能体)自身所在的实时 Cordis 运行时,可检查已加载的插件和服务接口、挂载模型编写的插件,并将其 dispose(资源释放)——外加受限 repository Plugin 运行时。该组也是未来自我修改类包的落点。设计说明见[工具集 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。 + +| 包(package) | 角色 | ctx 键 | +|---|---|---| +| [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect`/`cordis_mount`/`cordis_unmount` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理内存中的临时插件 | 注册到 `ctx.tools` | +| [`repository-plugin/`](repository-plugin/README.md) | 通过 DSH 自有子 Plugin 准备并挂载静态 repository skills 与通用 `.mcp.json` server | 注册一个 Loader builtin | diff --git a/packages/cordis/repository-plugin/README.i18n.yaml b/packages/self-modification/repository-plugin/README.i18n.yaml similarity index 77% rename from packages/cordis/repository-plugin/README.i18n.yaml rename to packages/self-modification/repository-plugin/README.i18n.yaml index daf43fa018..e0ce60a641 100644 --- a/packages/cordis/repository-plugin/README.i18n.yaml +++ b/packages/self-modification/repository-plugin/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/cordis/repository-plugin/README.md +# pnpm run verify-translation-pairing --write packages/self-modification/repository-plugin/README.md README.md: 33cd763d7dbe21b72f9e604b7b2e313081cf656f README.zh.md: 903dfbe601cc76acb0c1e87453dc03ef0321409b diff --git a/packages/cordis/repository-plugin/README.md b/packages/self-modification/repository-plugin/README.md similarity index 100% rename from packages/cordis/repository-plugin/README.md rename to packages/self-modification/repository-plugin/README.md diff --git a/packages/cordis/repository-plugin/README.zh.md b/packages/self-modification/repository-plugin/README.zh.md similarity index 100% rename from packages/cordis/repository-plugin/README.zh.md rename to packages/self-modification/repository-plugin/README.zh.md diff --git a/packages/cordis/repository-plugin/package.json b/packages/self-modification/repository-plugin/package.json similarity index 100% rename from packages/cordis/repository-plugin/package.json rename to packages/self-modification/repository-plugin/package.json diff --git a/packages/cordis/repository-plugin/src/bin.ts b/packages/self-modification/repository-plugin/src/bin.ts similarity index 100% rename from packages/cordis/repository-plugin/src/bin.ts rename to packages/self-modification/repository-plugin/src/bin.ts diff --git a/packages/cordis/repository-plugin/src/format.ts b/packages/self-modification/repository-plugin/src/format.ts similarity index 100% rename from packages/cordis/repository-plugin/src/format.ts rename to packages/self-modification/repository-plugin/src/format.ts diff --git a/packages/cordis/repository-plugin/src/index.ts b/packages/self-modification/repository-plugin/src/index.ts similarity index 100% rename from packages/cordis/repository-plugin/src/index.ts rename to packages/self-modification/repository-plugin/src/index.ts diff --git a/packages/cordis/repository-plugin/src/invariant.ts b/packages/self-modification/repository-plugin/src/invariant.ts similarity index 100% rename from packages/cordis/repository-plugin/src/invariant.ts rename to packages/self-modification/repository-plugin/src/invariant.ts diff --git a/packages/cordis/repository-plugin/src/mcp.ts b/packages/self-modification/repository-plugin/src/mcp.ts similarity index 100% rename from packages/cordis/repository-plugin/src/mcp.ts rename to packages/self-modification/repository-plugin/src/mcp.ts diff --git a/packages/cordis/repository-plugin/src/source.ts b/packages/self-modification/repository-plugin/src/source.ts similarity index 98% rename from packages/cordis/repository-plugin/src/source.ts rename to packages/self-modification/repository-plugin/src/source.ts index 3ccddc6c52..0befb82e42 100644 --- a/packages/cordis/repository-plugin/src/source.ts +++ b/packages/self-modification/repository-plugin/src/source.ts @@ -11,7 +11,7 @@ import { resolveDshHome } from '@deepseek-ai/dsh-paths' import { PREPARED_ENTRY_FILENAME } from './format.ts' // Value mirror: Cordis's const enum has no runtime object to import. Keep -// aligned with `packages/cordis/tool-cordis/src/fiber-state.ts`. +// aligned with `packages/self-modification/tool-cordis/src/fiber-state.ts`. const FIBER_ACTIVE = 2 as FiberState.ACTIVE /** Directory under the Harness home containing immutable repository generations. */ diff --git a/packages/cordis/repository-plugin/tests/mcp-format.spec.ts b/packages/self-modification/repository-plugin/tests/mcp-format.spec.ts similarity index 100% rename from packages/cordis/repository-plugin/tests/mcp-format.spec.ts rename to packages/self-modification/repository-plugin/tests/mcp-format.spec.ts diff --git a/packages/cordis/repository-plugin/tests/repository-plugin.spec.ts b/packages/self-modification/repository-plugin/tests/repository-plugin.spec.ts similarity index 100% rename from packages/cordis/repository-plugin/tests/repository-plugin.spec.ts rename to packages/self-modification/repository-plugin/tests/repository-plugin.spec.ts diff --git a/packages/cordis/repository-plugin/tsconfig.json b/packages/self-modification/repository-plugin/tsconfig.json similarity index 100% rename from packages/cordis/repository-plugin/tsconfig.json rename to packages/self-modification/repository-plugin/tsconfig.json diff --git a/packages/cordis/repository-plugin/tsdown.config.ts b/packages/self-modification/repository-plugin/tsdown.config.ts similarity index 100% rename from packages/cordis/repository-plugin/tsdown.config.ts rename to packages/self-modification/repository-plugin/tsdown.config.ts diff --git a/packages/cordis/tool-cordis/README.i18n.yaml b/packages/self-modification/tool-cordis/README.i18n.yaml similarity index 78% rename from packages/cordis/tool-cordis/README.i18n.yaml rename to packages/self-modification/tool-cordis/README.i18n.yaml index fd2b5443ef..c25dda28e3 100644 --- a/packages/cordis/tool-cordis/README.i18n.yaml +++ b/packages/self-modification/tool-cordis/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/cordis/tool-cordis/README.md +# pnpm run verify-translation-pairing --write packages/self-modification/tool-cordis/README.md README.md: 9986310160c2b56126155a4c3ef84d66018d31c2 README.zh.md: d955306e1e5d4154f58c771704782ece44a15c99 diff --git a/packages/cordis/tool-cordis/README.md b/packages/self-modification/tool-cordis/README.md similarity index 100% rename from packages/cordis/tool-cordis/README.md rename to packages/self-modification/tool-cordis/README.md diff --git a/packages/cordis/tool-cordis/README.zh.md b/packages/self-modification/tool-cordis/README.zh.md similarity index 100% rename from packages/cordis/tool-cordis/README.zh.md rename to packages/self-modification/tool-cordis/README.zh.md diff --git a/packages/cordis/tool-cordis/package.json b/packages/self-modification/tool-cordis/package.json similarity index 100% rename from packages/cordis/tool-cordis/package.json rename to packages/self-modification/tool-cordis/package.json diff --git a/packages/cordis/tool-cordis/src/api-catalog.ts b/packages/self-modification/tool-cordis/src/api-catalog.ts similarity index 100% rename from packages/cordis/tool-cordis/src/api-catalog.ts rename to packages/self-modification/tool-cordis/src/api-catalog.ts diff --git a/packages/cordis/tool-cordis/src/fiber-state.ts b/packages/self-modification/tool-cordis/src/fiber-state.ts similarity index 100% rename from packages/cordis/tool-cordis/src/fiber-state.ts rename to packages/self-modification/tool-cordis/src/fiber-state.ts diff --git a/packages/cordis/tool-cordis/src/guard.ts b/packages/self-modification/tool-cordis/src/guard.ts similarity index 100% rename from packages/cordis/tool-cordis/src/guard.ts rename to packages/self-modification/tool-cordis/src/guard.ts diff --git a/packages/cordis/tool-cordis/src/index.ts b/packages/self-modification/tool-cordis/src/index.ts similarity index 100% rename from packages/cordis/tool-cordis/src/index.ts rename to packages/self-modification/tool-cordis/src/index.ts diff --git a/packages/cordis/tool-cordis/src/inspect.ts b/packages/self-modification/tool-cordis/src/inspect.ts similarity index 100% rename from packages/cordis/tool-cordis/src/inspect.ts rename to packages/self-modification/tool-cordis/src/inspect.ts diff --git a/packages/cordis/tool-cordis/src/invariant.ts b/packages/self-modification/tool-cordis/src/invariant.ts similarity index 100% rename from packages/cordis/tool-cordis/src/invariant.ts rename to packages/self-modification/tool-cordis/src/invariant.ts diff --git a/packages/cordis/tool-cordis/src/mount.ts b/packages/self-modification/tool-cordis/src/mount.ts similarity index 100% rename from packages/cordis/tool-cordis/src/mount.ts rename to packages/self-modification/tool-cordis/src/mount.ts diff --git a/packages/cordis/tool-cordis/src/present.ts b/packages/self-modification/tool-cordis/src/present.ts similarity index 100% rename from packages/cordis/tool-cordis/src/present.ts rename to packages/self-modification/tool-cordis/src/present.ts diff --git a/packages/cordis/tool-cordis/src/sandbox.ts b/packages/self-modification/tool-cordis/src/sandbox.ts similarity index 100% rename from packages/cordis/tool-cordis/src/sandbox.ts rename to packages/self-modification/tool-cordis/src/sandbox.ts diff --git a/packages/cordis/tool-cordis/tests/cordis-lifecycle.spec.ts b/packages/self-modification/tool-cordis/tests/cordis-lifecycle.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/cordis-lifecycle.spec.ts rename to packages/self-modification/tool-cordis/tests/cordis-lifecycle.spec.ts diff --git a/packages/cordis/tool-cordis/tests/cross-mount.spec.ts b/packages/self-modification/tool-cordis/tests/cross-mount.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/cross-mount.spec.ts rename to packages/self-modification/tool-cordis/tests/cross-mount.spec.ts diff --git a/packages/cordis/tool-cordis/tests/helpers.ts b/packages/self-modification/tool-cordis/tests/helpers.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/helpers.ts rename to packages/self-modification/tool-cordis/tests/helpers.ts diff --git a/packages/cordis/tool-cordis/tests/inspect.spec.ts b/packages/self-modification/tool-cordis/tests/inspect.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/inspect.spec.ts rename to packages/self-modification/tool-cordis/tests/inspect.spec.ts diff --git a/packages/cordis/tool-cordis/tests/integration.spec.ts b/packages/self-modification/tool-cordis/tests/integration.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/integration.spec.ts rename to packages/self-modification/tool-cordis/tests/integration.spec.ts diff --git a/packages/cordis/tool-cordis/tests/mount.spec.ts b/packages/self-modification/tool-cordis/tests/mount.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/mount.spec.ts rename to packages/self-modification/tool-cordis/tests/mount.spec.ts diff --git a/packages/cordis/tool-cordis/tests/present.spec.ts b/packages/self-modification/tool-cordis/tests/present.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/present.spec.ts rename to packages/self-modification/tool-cordis/tests/present.spec.ts diff --git a/packages/cordis/tool-cordis/tests/sandbox-context.spec.ts b/packages/self-modification/tool-cordis/tests/sandbox-context.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/sandbox-context.spec.ts rename to packages/self-modification/tool-cordis/tests/sandbox-context.spec.ts diff --git a/packages/cordis/tool-cordis/tests/tool-cordis.spec.ts b/packages/self-modification/tool-cordis/tests/tool-cordis.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/tool-cordis.spec.ts rename to packages/self-modification/tool-cordis/tests/tool-cordis.spec.ts diff --git a/packages/cordis/tool-cordis/tests/unmount-hmr.spec.ts b/packages/self-modification/tool-cordis/tests/unmount-hmr.spec.ts similarity index 100% rename from packages/cordis/tool-cordis/tests/unmount-hmr.spec.ts rename to packages/self-modification/tool-cordis/tests/unmount-hmr.spec.ts diff --git a/packages/cordis/tool-cordis/tsconfig.json b/packages/self-modification/tool-cordis/tsconfig.json similarity index 100% rename from packages/cordis/tool-cordis/tsconfig.json rename to packages/self-modification/tool-cordis/tsconfig.json diff --git a/packages/timeout/README.i18n.yaml b/packages/timeout/README.i18n.yaml deleted file mode 100644 index 33a7a5b26d..0000000000 --- a/packages/timeout/README.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/timeout/README.md -README.md: d9d8e8a829d8d466d1cc3e42a6c32ac4b3db428b -README.zh.md: c5920258ade7022fb5b28a351ba060eb56e8bc3c diff --git a/packages/timeout/README.md b/packages/timeout/README.md deleted file mode 100644 index d9d8e8a829..0000000000 --- a/packages/timeout/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# timeout/ — tool-call timeout policy - -English | [中文](README.zh.md) - -This group applies deployment-configured deadlines to model-facing tool calls. Capabilities remain responsible for terminating their own work. - -| Package | Role | -|---|---| -| [`timeout-policy/`](timeout-policy/README.md) | Enforces configured per-tool call deadlines | - -The pure timing primitives live in [`util/timeout`](../util/timeout/README.md). See the [timeout-library decision](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md). diff --git a/packages/timeout/README.zh.md b/packages/timeout/README.zh.md deleted file mode 100644 index c5920258ad..0000000000 --- a/packages/timeout/README.zh.md +++ /dev/null @@ -1,11 +0,0 @@ -# timeout/:工具调用超时策略 - -[English](README.md) | 中文 - -本分组将部署配置的截止时间应用于面向模型的工具调用。各项能力仍负责终止自身工作。 - -| 包 | 职责 | -|---|---| -| [`timeout-policy/`](timeout-policy/README.md) | 强制执行配置的逐工具调用截止时间 | - -纯计时原语位于 [`util/timeout`](../util/timeout/README.md)。参见[超时库决策](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)。 diff --git a/packages/typert/generator/tests/cordis-catalog.spec.ts b/packages/typert/generator/tests/cordis-catalog.spec.ts index 6e6b93dfdc..644bb28de2 100644 --- a/packages/typert/generator/tests/cordis-catalog.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog.spec.ts @@ -18,7 +18,7 @@ describe('Typert-backed Cordis catalog', () => { expect(renderEvents([...model.events], CORDIS_CATALOG_POLICY)).toBe(expected('docs/cordis-catalog/events.md')) expect(renderServices([...model.services], CORDIS_CATALOG_POLICY)).toBe(expected('docs/cordis-catalog/services.md')) expect(projector.renderRuntimeApi(model)).toBe( - expected('packages/cordis/tool-cordis/src/api-catalog.ts'), + expected('packages/self-modification/tool-cordis/src/api-catalog.ts'), ) }) }) diff --git a/packages/web/tool-web/README.i18n.yaml b/packages/web/tool-web/README.i18n.yaml index eb57a7b20a..4751678c04 100644 --- a/packages/web/tool-web/README.i18n.yaml +++ b/packages/web/tool-web/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/web/tool-web/README.md -README.md: 791ea87c655444e639ef85ccce737066ead8b749 -README.zh.md: ffcf2d9813dcb94d8106b2c7d5f8ee9fc25e1aaa +README.md: 646601bb5bd6c27876918b7aca49962ae34bae33 +README.zh.md: 192b7020f5fac026f325448630b2e17d7ce6a0c4 diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index 791ea87c65..646601bb5b 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The model-facing web tool suite — `web_search` and `web_fetch` — over the [web capability seam](../web/README.md) (`ctx.web`). It owns model-facing concerns only: tool names, JSON schemas, snake_case argument names, prompt sections, the result-count bound, result formatting, HTML→markdown presentation, and the UI presentation projection — `presentCall`, `presentResult` (a `card: 'web'` result card discriminated by `kind: 'search' | 'fetch'`), and the `output.presentationMeta` that carries the structured search sources or the fetch summary the lossy render text cannot (see the [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md)). All web access goes through `ctx.web`; this package never imports a concrete provider. Neither tool exposes a model-facing timeout — each tool's cooperative tool-call budget is declared here via config (`fetchTimeoutMs`/`searchTimeoutMs`, attached as `ToolDefinition.timeoutMs`) and enforced by [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md) (a `tools/execute` wrapper); each tool just forwards `exec.signal` to the seam. +The model-facing web tool suite — `web_search` and `web_fetch` — over the [web capability seam](../web/README.md) (`ctx.web`). It owns model-facing concerns only: tool names, JSON schemas, snake_case argument names, prompt sections, the result-count bound, result formatting, HTML→markdown presentation, and the UI presentation projection — `presentCall`, `presentResult` (a `card: 'web'` result card discriminated by `kind: 'search' | 'fetch'`), and the `output.presentationMeta` that carries the structured search sources or the fetch summary the lossy render text cannot (see the [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md)). All web access goes through `ctx.web`; this package never imports a concrete provider. Neither tool exposes a model-facing timeout — each tool's cooperative tool-call budget is declared here via config (`fetchTimeoutMs`/`searchTimeoutMs`, attached as `ToolDefinition.timeoutMs`) and enforced by [`@deepseek-ai/dsh-timeout-policy`](../../guard/timeout-policy/README.md) (a `tools/execute` wrapper); each tool just forwards `exec.signal` to the seam. Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`). Search guidance mentions `web_fetch` only when fetch is also config-enabled; a search-only composition instead tells the model to use returned snippets and cite their URLs. @@ -28,7 +28,7 @@ The normalized seam results are also the canonical tool values: `WebSearchResult | `searchTimeoutMs` | `30000` | Cooperative tool-call timeout budget (ms) for `web_search`. | | `fetchMaxOutputChars` | `200000` | Cap on source characters converted synchronously and on one complete `web_fetch` output (header, rendered body, and footer); a cut body gets the truncation notice when it fits. | -`fetchTimeoutMs`/`searchTimeoutMs` declare each tool's cooperative timeout budget (attached as `ToolDefinition.timeoutMs`), enforced by [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md); the model-facing schema exposes no timeout argument. `fetchMaxOutputChars` bounds both synchronous conversion work and the complete rendered result: only that many source characters are converted, and the header, converted prefix, and truncation notice are then capped together. The default leaves headroom above the local provider's 100,000-character body cap, but rendered expansion can still make the final bound truncate the result. +`fetchTimeoutMs`/`searchTimeoutMs` declare each tool's cooperative timeout budget (attached as `ToolDefinition.timeoutMs`), enforced by [`@deepseek-ai/dsh-timeout-policy`](../../guard/timeout-policy/README.md); the model-facing schema exposes no timeout argument. `fetchMaxOutputChars` bounds both synchronous conversion work and the complete rendered result: only that many source characters are converted, and the header, converted prefix, and truncation notice are then capped together. The default leaves headroom above the local provider's 100,000-character body cap, but rendered expansion can still make the final bound truncate the result. ```yaml - id: tool-web diff --git a/packages/web/tool-web/README.zh.md b/packages/web/tool-web/README.zh.md index ffcf2d9813..192b7020f5 100644 --- a/packages/web/tool-web/README.zh.md +++ b/packages/web/tool-web/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -面向模型的 web 工具套件 `web_search` 与 `web_fetch`,构建于 [web 能力 seam](../web/README.md)(`ctx.web`)之上。它只负责面向模型的事项:工具名称、JSON Schema、snake_case 参数名称、提示词区段、结果数量上限、结果格式、HTML→markdown 呈现,以及 UI 呈现投影——`presentCall`、`presentResult`(以 `kind: 'search' | 'fetch'` 区分的 `card: 'web'` 结果卡片),以及承载有损渲染文本无法携带的结构化搜索来源或抓取摘要的 `output.presentationMeta`(见 [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md))。所有 web 访问都通过 `ctx.web`;该包绝不导入具体提供方。两个工具都不公开面向模型的超时:每个工具的协作式工具调用超时预算通过配置在此声明(`fetchTimeoutMs`/`searchTimeoutMs`,附加为 `ToolDefinition.timeoutMs`),由 [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md)(`tools/execute` 包装层)强制执行;每个工具只把 `exec.signal` 转发给 seam。 +面向模型的 web 工具套件 `web_search` 与 `web_fetch`,构建于 [web 能力 seam](../web/README.md)(`ctx.web`)之上。它只负责面向模型的事项:工具名称、JSON Schema、snake_case 参数名称、提示词区段、结果数量上限、结果格式、HTML→markdown 呈现,以及 UI 呈现投影——`presentCall`、`presentResult`(以 `kind: 'search' | 'fetch'` 区分的 `card: 'web'` 结果卡片),以及承载有损渲染文本无法携带的结构化搜索来源或抓取摘要的 `output.presentationMeta`(见 [web-result-card Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-web-result-card.md))。所有 web 访问都通过 `ctx.web`;该包绝不导入具体提供方。两个工具都不公开面向模型的超时:每个工具的协作式工具调用超时预算通过配置在此声明(`fetchTimeoutMs`/`searchTimeoutMs`,附加为 `ToolDefinition.timeoutMs`),由 [`@deepseek-ai/dsh-timeout-policy`](../../guard/timeout-policy/README.md)(`tools/execute` 包装层)强制执行;每个工具只把 `exec.signal` 转发给 seam。 每个工具独立注册;只需要其中一个工具的产品可以通过配置禁用另一个(`{ search: false }`/`{ fetch: false }`)。仅当抓取也通过配置启用时,搜索指引才会提及 `web_fetch`;仅启用搜索的组合则会要求模型使用返回的 snippet 并引用其 URL。 @@ -28,7 +28,7 @@ | `searchTimeoutMs` | `30000` | `web_search` 的协作式工具调用超时预算(ms)。 | | `fetchMaxOutputChars` | `200000` | 同步转换的源字符数与单次完整 `web_fetch` 输出的上限(状态头、渲染后的主体与页脚合并计算);主体被截断时,在能容纳的情况下附带截断提示。 | -`fetchTimeoutMs`/`searchTimeoutMs` 声明每个工具的协作式超时预算(附加为 `ToolDefinition.timeoutMs`),由 [`@deepseek-ai/dsh-timeout-policy`](../../timeout/timeout-policy/README.md) 强制执行;面向模型的 schema 不公开超时参数。`fetchMaxOutputChars` 同时限制同步转换工作量和完整渲染结果:只转换至多该数量的源字符,随后对状态头、转换后的前缀和截断提示合并设限。默认值为本地提供方的 100,000 字符主体上限留出余量,但渲染膨胀仍可能使最终上限截断结果。 +`fetchTimeoutMs`/`searchTimeoutMs` 声明每个工具的协作式超时预算(附加为 `ToolDefinition.timeoutMs`),由 [`@deepseek-ai/dsh-timeout-policy`](../../guard/timeout-policy/README.md) 强制执行;面向模型的 schema 不公开超时参数。`fetchMaxOutputChars` 同时限制同步转换工作量和完整渲染结果:只转换至多该数量的源字符,随后对状态头、转换后的前缀和截断提示合并设限。默认值为本地提供方的 100,000 字符主体上限留出余量,但渲染膨胀仍可能使最终上限截断结果。 ```yaml - id: tool-web diff --git a/packages/web/tool-web/tsconfig.json b/packages/web/tool-web/tsconfig.json index f6684272ce..232d6f6c50 100644 --- a/packages/web/tool-web/tsconfig.json +++ b/packages/web/tool-web/tsconfig.json @@ -25,7 +25,7 @@ "path": "../../core/system-prompt" }, { - "path": "../../timeout/timeout-policy" + "path": "../../guard/timeout-policy" }, { "path": "../web" diff --git a/packages/web/web-fetch-local/README.i18n.yaml b/packages/web/web-fetch-local/README.i18n.yaml index 0e487b6ef5..5511f72975 100644 --- a/packages/web/web-fetch-local/README.i18n.yaml +++ b/packages/web/web-fetch-local/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/web/web-fetch-local/README.md -README.md: 8cadba2de7a2708252ebc7143840825fd4fe4549 -README.zh.md: b3f0707c448da7abaaafe2cdcf2520914ffff932 +README.md: 531bfc94a4b2867b1a81d2e0b398ec403186b347 +README.zh.md: 260227679b72ec993dbb9e42a9d2a814bf691e2c diff --git a/packages/web/web-fetch-local/README.md b/packages/web/web-fetch-local/README.md index 8cadba2de7..531bfc94a4 100644 --- a/packages/web/web-fetch-local/README.md +++ b/packages/web/web-fetch-local/README.md @@ -10,7 +10,7 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i The provider owns **safe resource retrieval**: URL validation, HTTP transport, redirect policy, a resource-backstop timeout, abort propagation, byte caps, charset decoding, content-type classification, and binary rejection. `@deepseek-ai/dsh-tool-web` owns **presentation** (HTML→markdown, truncation formatting). A non-2xx HTTP response is a *result* (status code + decoded body), not an error; `WebError` is reserved for failures to safely retrieve or represent the resource. -The provider's `timeoutMs` is a resource backstop for direct `ctx.web.fetch()` callers and misconfigured deployments, not the model-facing tool-call budget. [`dsh-timeout-policy`](../../timeout/timeout-policy/README.md) owns the `web_fetch` tool-call budget by arming `exec.signal`. +The provider's `timeoutMs` is a resource backstop for direct `ctx.web.fetch()` callers and misconfigured deployments, not the model-facing tool-call budget. [`dsh-timeout-policy`](../../guard/timeout-policy/README.md) owns the `web_fetch` tool-call budget by arming `exec.signal`. A shipping web-tool deployment sets the provider backstop above the tool budget, so model calls normally return `TOOL_TIMEOUT`. If the outer deadline reaches the provider first, the provider reports `WEB_ABORTED` and the outer policy replaces it with `TOOL_TIMEOUT`. `WEB_FETCH_TIMEOUT` therefore identifies a direct seam caller whose provider budget elapsed. diff --git a/packages/web/web-fetch-local/README.zh.md b/packages/web/web-fetch-local/README.zh.md index b3f0707c44..260227679b 100644 --- a/packages/web/web-fetch-local/README.zh.md +++ b/packages/web/web-fetch-local/README.zh.md @@ -10,7 +10,7 @@ 提供方拥有**安全资源获取**:URL 验证、HTTP 传输、重定向策略、资源兜底超时、中止传播、字节上限、charset 解码、内容类型分类与二进制拒绝。`@deepseek-ai/dsh-tool-web` 拥有**呈现**(HTML→markdown、截断格式)。非 2xx HTTP 响应是*结果*(状态码 + 解码主体),不是错误;`WebError` 只用于无法安全获取或表示资源的失败。 -提供方的 `timeoutMs` 是直接 `ctx.web.fetch()` 调用方和配置有误的部署所用的资源兜底,不是面向模型的工具调用预算。[`dsh-timeout-policy`](../../timeout/timeout-policy/README.md) 拥有 `web_fetch` 工具调用预算,并让 `exec.signal` 在超时时触发,以强制执行该预算。 +提供方的 `timeoutMs` 是直接 `ctx.web.fetch()` 调用方和配置有误的部署所用的资源兜底,不是面向模型的工具调用预算。[`dsh-timeout-policy`](../../guard/timeout-policy/README.md) 拥有 `web_fetch` 工具调用预算,并让 `exec.signal` 在超时时触发,以强制执行该预算。 已交付的 web 工具部署会把提供方兜底设为高于工具预算,因此模型调用通常返回 `TOOL_TIMEOUT`。如果外层截止期限先于提供方的兜底超时触发,提供方会报告 `WEB_ABORTED`,外层策略再将其替换为 `TOOL_TIMEOUT`。因此,`WEB_FETCH_TIMEOUT` 表明直接 seam 调用方的提供方预算已经耗尽。 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 08019b567e..302ec7fdd9 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -16,7 +16,7 @@ import { renderCordisCoreApiPages } from './cordis-core-api.ts' const root = resolve(import.meta.dirname, '..') const OUT_EVENTS = 'docs/cordis-catalog/events.md' const OUT_SERVICES = 'docs/cordis-catalog/services.md' -const OUT_RUNTIME_API = 'packages/cordis/tool-cordis/src/api-catalog.ts' +const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts' /** One primary core-data-structures page per project type used by a generated signature. */ export const LINK_MAP: Readonly> = { diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 06475808e1..55ec5eb57f 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -226,7 +226,7 @@ const TOOL_PACKAGES: ToolPackage[] = [ { pkg: '@deepseek-ai/dsh-tool-cordis', dir: 'tool-cordis', - source: 'packages/cordis/tool-cordis/src/index.ts', + source: 'packages/self-modification/tool-cordis/src/index.ts', requires: ['ctx.tools'], writes: ['tool/call', 'tool/result', 'process-local temporary Plugin lifecycle'], async mount(ctx) { diff --git a/tsconfig.base.json b/tsconfig.base.json index b748020e11..d8b777a2ad 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -101,10 +101,9 @@ "./packages/workflow/*/src/invariant.ts", "./packages/web/*/src/invariant.ts", "./packages/spill/*/src/invariant.ts", - "./packages/timeout/*/src/invariant.ts", "./packages/todo/*/src/invariant.ts", "./packages/bundle/*/src/invariant.ts", - "./packages/cordis/*/src/invariant.ts", + "./packages/self-modification/*/src/invariant.ts", "./packages/sandbox/*/src/invariant.ts", "./packages/hooks/*/src/invariant.ts", "./packages/session/*/src/invariant.ts", @@ -210,10 +209,9 @@ "./packages/workflow/*/src", "./packages/web/*/src", "./packages/spill/*/src", - "./packages/timeout/*/src", "./packages/todo/*/src", "./packages/bundle/*/src", - "./packages/cordis/*/src", + "./packages/self-modification/*/src", "./packages/sandbox/*/src", "./packages/hooks/*/src", "./packages/session/*/src", diff --git a/tsconfig.host.json b/tsconfig.host.json index c1cc0705cf..31631995cf 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -204,7 +204,7 @@ { "path": "./packages/spill/spill" }, { "path": "./packages/spill/spill-local" }, { "path": "./packages/spill/spill-policy" }, - { "path": "./packages/timeout/timeout-policy" }, + { "path": "./packages/guard/timeout-policy" }, { "path": "./packages/support/invariants" }, { "path": "./packages/support/agent-loop-testkit" }, { "path": "./packages/acp/acp" }, @@ -241,8 +241,8 @@ { "path": "./packages/todo/tool-todo" }, { "path": "./packages/plan/plan-mode" }, { "path": "./packages/guard/repeat-tool-guard" }, - { "path": "./packages/cordis/tool-cordis" }, - { "path": "./packages/cordis/repository-plugin" }, + { "path": "./packages/self-modification/tool-cordis" }, + { "path": "./packages/self-modification/repository-plugin" }, { "path": "./packages/hooks/hook-protocol" }, { "path": "./packages/hooks/hooks-claude" }, { "path": "./packages/hooks/hooks-codex" }, From 36cf4f32f4817d6865c1454d6cd5a18c4bb4cf77 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 03:48:07 +0800 Subject: [PATCH 05/19] docs(agent-note): flip the regrouping note to implemented Move the triplet to implemented/architecture/, rewrite Proposal into a present-tense Decision, fold Acceptance criteria and Risks into Consequences (recording what pins the result), retitle the migration section to what the move actually touched, and repoint the seven inbound FIXME/README links. --- ...026-07-27-tui-chat-channel-module-split.md | 2 +- ...-07-27-tui-chat-channel-module-split.zh.md | 2 +- ...-07-24-tui-turn-end-stop-reason-notices.md | 2 +- ...-24-tui-turn-end-stop-reason-notices.zh.md | 2 +- ...17-dedicated-full-screen-tui-front-door.md | 2 +- ...dedicated-full-screen-tui-front-door.zh.md | 2 +- .../feature/2026-07-20-windows-tui-support.md | 2 +- .../2026-07-20-windows-tui-support.zh.md | 2 +- .../2026-07-21-tui-skill-slash-command.md | 2 +- .../2026-07-21-tui-skill-slash-command.zh.md | 2 +- ...-07-27-assistant-timing-header-trailing.md | 2 +- ...-27-assistant-timing-header-trailing.zh.md | 2 +- ...sh-guided-skill-session-commands.i18n.yaml | 6 +- ...07-28-dsh-guided-skill-session-commands.md | 2 +- ...28-dsh-guided-skill-session-commands.zh.md | 2 +- ...7-26-execa-for-test-subprocess-plumbing.md | 2 +- ...6-execa-for-test-subprocess-plumbing.zh.md | 2 +- .../2026-07-29-package-regrouping.i18n.yaml | 6 + .../2026-07-29-package-regrouping.md | 60 +- .../2026-07-29-package-regrouping.zh.md | 52 +- ...-yaml-and-user-environment-layer.i18n.yaml | 4 +- ...entials-yaml-and-user-environment-layer.md | 4 +- ...ials-yaml-and-user-environment-layer.zh.md | 4 +- ...-fail-loud-releases-the-terminal.i18n.yaml | 4 +- ...6-07-31-fail-loud-releases-the-terminal.md | 2 +- ...7-31-fail-loud-releases-the-terminal.zh.md | 2 +- ...3-cli-signal-shutdown-escalation.i18n.yaml | 4 +- ...26-08-03-cli-signal-shutdown-escalation.md | 2 +- ...08-03-cli-signal-shutdown-escalation.zh.md | 2 +- ...3-hmr-initial-scan-boot-deadlock.i18n.yaml | 4 +- ...26-08-03-hmr-initial-scan-boot-deadlock.md | 2 +- ...08-03-hmr-initial-scan-boot-deadlock.zh.md | 2 +- .../2026-07-28-feedback-command.i18n.yaml | 4 +- .../feature/2026-07-28-feedback-command.md | 2 +- .../feature/2026-07-28-feedback-command.zh.md | 2 +- ...-domain-kv-storage-and-workspace.i18n.yaml | 2 +- ...7-24-domain-kv-storage-and-workspace.zh.md | 2 +- .../2026-07-29-package-regrouping.i18n.yaml | 6 - AGENTS.md | 26 +- apps/cli/reference/README.i18n.yaml | 4 +- apps/cli/reference/README.md | 2 +- apps/cli/reference/README.zh.md | 2 +- docs/config-catalog.md | 62 +- docs/cordis-catalog/events.md | 6 +- docs/cordis-catalog/services.md | 18 +- docs/event-producer-consumer.md | 28 +- docs/module-graph.md | 354 +++-- docs/persistence-catalog.md | 12 +- examples/web-cordis/README.i18n.yaml | 4 +- examples/web-cordis/README.md | 4 +- examples/web-cordis/README.zh.md | 4 +- packages/api/remotes/tsconfig.host.json | 2 +- packages/bash/bash-env/tsconfig.json | 2 +- packages/bundle/README.i18n.yaml | 10 +- packages/bundle/README.md | 2 +- packages/bundle/README.zh.md | 2 +- .../compact/command-compact/README.i18n.yaml | 4 +- packages/compact/command-compact/README.md | 2 +- packages/compact/command-compact/README.zh.md | 2 +- .../credentials-local/README.i18n.yaml | 4 +- .../credentials/credentials-local/README.md | 2 +- .../credentials-local/README.zh.md | 2 +- .../examples/jsonrpc-demo/README.i18n.yaml | 4 +- packages/examples/jsonrpc-demo/README.md | 4 +- packages/examples/jsonrpc-demo/README.zh.md | 4 +- packages/feedback/README.i18n.yaml | 4 +- packages/feedback/README.md | 2 +- packages/feedback/README.zh.md | 2 +- .../command-feedback/README.i18n.yaml | 4 +- packages/feedback/command-feedback/README.md | 6 +- .../feedback/command-feedback/README.zh.md | 6 +- .../feedback/command-feedback/tsconfig.json | 2 +- packages/goal/command-goal/README.i18n.yaml | 4 +- packages/goal/command-goal/README.md | 2 +- packages/goal/command-goal/README.zh.md | 2 +- packages/guard/timeout-policy/src/index.ts | 2 +- .../interaction/permission/README.i18n.yaml | 2 +- .../user-interaction/README.i18n.yaml | 4 +- packages/plan/plan-mode/README.i18n.yaml | 4 +- packages/plan/plan-mode/README.md | 2 +- packages/plan/plan-mode/README.zh.md | 2 +- packages/scaffold/README.i18n.yaml | 4 +- packages/scaffold/README.md | 2 +- packages/scaffold/README.zh.md | 2 +- packages/scaffold/client/README.i18n.yaml | 4 +- packages/scaffold/client/README.md | 2 +- packages/scaffold/client/README.zh.md | 2 +- packages/scaffold/helper/src/index.ts | 2 +- packages/scaffold/protocol/README.i18n.yaml | 4 +- packages/scaffold/protocol/README.md | 6 +- packages/scaffold/protocol/README.zh.md | 6 +- packages/scaffold/scripts/src/index.ts | 2 +- packages/scaffold/server/README.i18n.yaml | 4 +- packages/scaffold/server/src/index.ts | 2 +- packages/scaffold/telemetry/src/index.ts | 3 +- .../subagent/subagent-codex/tsconfig.json | 2 +- packages/subagent/subagent/tsconfig.json | 4 +- .../typert/generator/tests/type-model.spec.ts | 4 +- packages/util/environment/README.i18n.yaml | 4 +- packages/util/environment/README.md | 2 +- packages/util/environment/README.zh.md | 2 +- pnpm-lock.yaml | 1284 ++++++++--------- .../request-response.expected.json | 4 +- scripts/type-equiv.manifest.json | 2 +- scripts/verify-package-paths.ts | 16 +- 105 files changed, 1089 insertions(+), 1112 deletions(-) create mode 100644 .agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml rename .agents/notes/{proposed => implemented}/architecture/2026-07-29-package-regrouping.md (56%) rename .agents/notes/{proposed => implemented}/architecture/2026-07-29-package-regrouping.zh.md (66%) delete mode 100644 .agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml diff --git a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md index 9b2c456d88..3cf9548d4d 100644 --- a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md +++ b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.md @@ -7,7 +7,7 @@ English | [中文](2026-07-27-tui-chat-channel-module-split.zh.md) ## Problem -`packages/interaction/tui/src/index.ts` had grown past 2000 lines. Most of it was one `createTuiChat` factory: a ~1600-line closure holding roughly forty mutable variables and as many nested closures. Model selection, the ask-user-question queue, and session resume were tangled into that single scope, so a reader could not follow any one concern without holding the whole file in their head, and unrelated edits collided. A prior pass had grouped `src/` into `components/`, `session/`, `extension/`, but the entry file itself and the loose top-level input files (`autocomplete.ts`, `file-autocomplete.ts`, `skill-invocation.ts`, `xml-tool-output.ts`) were untouched. +`packages/ui/tui/src/index.ts` had grown past 2000 lines. Most of it was one `createTuiChat` factory: a ~1600-line closure holding roughly forty mutable variables and as many nested closures. Model selection, the ask-user-question queue, and session resume were tangled into that single scope, so a reader could not follow any one concern without holding the whole file in their head, and unrelated edits collided. A prior pass had grouped `src/` into `components/`, `session/`, `extension/`, but the entry file itself and the loose top-level input files (`autocomplete.ts`, `file-autocomplete.ts`, `skill-invocation.ts`, `xml-tool-output.ts`) were untouched. ## Decision diff --git a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md index 44e8ffeac4..d5de02bd0f 100644 --- a/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md +++ b/.agents/notes/archived/architecture/2026-07-27-tui-chat-channel-module-split.zh.md @@ -7,7 +7,7 @@ Archived: 2026-08-04 ## Problem -`packages/interaction/tui/src/index.ts` 已超过 2000 行,其中绝大部分是单个 `createTuiChat` 工厂:一个约 1600 行的闭包,持有约四十个可变变量以及同等数量的嵌套闭包。模型选择、ask-user-question 队列、会话恢复都缠绕在这一个作用域里,读者无法在不把整份文件装进脑子的前提下理清任何单一关注点,互不相关的改动也会彼此冲突。此前一轮已把 `src/` 归组为 `components/`、`session/`、`extension/`,但入口文件本身以及散落在顶层的输入相关文件(`autocomplete.ts`、`file-autocomplete.ts`、`skill-invocation.ts`、`xml-tool-output.ts`)未动。 +`packages/ui/tui/src/index.ts` 已超过 2000 行,其中绝大部分是单个 `createTuiChat` 工厂:一个约 1600 行的闭包,持有约四十个可变变量以及同等数量的嵌套闭包。模型选择、ask-user-question 队列、会话恢复都缠绕在这一个作用域里,读者无法在不把整份文件装进脑子的前提下理清任何单一关注点,互不相关的改动也会彼此冲突。此前一轮已把 `src/` 归组为 `components/`、`session/`、`extension/`,但入口文件本身以及散落在顶层的输入相关文件(`autocomplete.ts`、`file-autocomplete.ts`、`skill-invocation.ts`、`xml-tool-output.ts`)未动。 ## Decision diff --git a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md index 998e9dfcdc..62784ed7cd 100644 --- a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md +++ b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.md @@ -11,7 +11,7 @@ The TUI rendered transcript notices for `error`, `aborted`, `max-tokens`, `rejec ## Decision -The `turn/end` case in `packages/interaction/tui/src/index.ts` switches on the reason's discriminant and covers every kind: `completed` stays silent because the settled assistant message and its `Completed` timing header already present that outcome; `disposed` appends `Turn stopped: the agent was disposed.`; and the merge-extensible default appends `Turn ended: .` so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices. +The `turn/end` case in `packages/ui/tui/src/index.ts` switches on the reason's discriminant and covers every kind: `completed` stays silent because the settled assistant message and its `Completed` timing header already present that outcome; `disposed` appends `Turn stopped: the agent was disposed.`; and the merge-extensible default appends `Turn ended: .` so an unknown plugin-added outcome still names why the agent stopped. All other kinds keep their existing notices. ## Alternatives considered diff --git a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md index ad53755259..162ba4ed44 100644 --- a/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md +++ b/.agents/notes/archived/bug-fix/2026-07-24-tui-turn-end-stop-reason-notices.zh.md @@ -11,7 +11,7 @@ TUI 会为 `error`、`aborted`、`max-tokens`、`rejected`、`interrupted` 这 ## 决策 -`packages/interaction/tui/src/index.ts` 中的 `turn/end` 分支按 reason 的判别字段做 switch,覆盖每一种 kind:`completed` 保持沉默,因为已定稿的助手消息及其 `Completed` 计时头部已经呈现了这一结果;`disposed` 追加 `Turn stopped: the agent was disposed.`;merge 扩展的 default 分支追加 `Turn ended: .`,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。 +`packages/ui/tui/src/index.ts` 中的 `turn/end` 分支按 reason 的判别字段做 switch,覆盖每一种 kind:`completed` 保持沉默,因为已定稿的助手消息及其 `Completed` 计时头部已经呈现了这一结果;`disposed` 追加 `Turn stopped: the agent was disposed.`;merge 扩展的 default 分支追加 `Turn ended: .`,让未知的插件新增结果仍能点明 agent 停止的原因。其余各 kind 保留现有通知。 ## 备选方案 diff --git a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md index 184a05ed23..1a04e738a2 100644 --- a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md +++ b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.md @@ -15,7 +15,7 @@ The interactive channel must remain a Cordis plugin over the same agent, session ## Decision -DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior. +DeepSeek Harness ships [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) as a dedicated Cordis plugin. It owns terminal input and presentation only; agent lifecycle, session persistence, tool execution, and the model-facing question tool remain separate composition entries. The plugin requires both stdin and stdout to be TTYs and fails instead of silently changing to line-oriented behavior. The package is a terminal front door, not a complete application. A host mounts `@deepseek-ai/dsh-tui` before its configured agent and composes the backends, tools, and policies around it. The product CLI currently ships no terminal composition; non-interactive tasks use headless mode, Web owns the installed human surface, and ACP remains a separate automation protocol. diff --git a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md index 6ac3eefa51..75fc3d47aa 100644 --- a/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md +++ b/.agents/notes/archived/feature/2026-07-17-dedicated-full-screen-tui-front-door.zh.md @@ -15,7 +15,7 @@ Archived: 2026-08-04 ## 决策 -DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。 +DeepSeek Harness 将 [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 作为独立的 Cordis 插件交付。该插件只负责终端输入与呈现;agent 生命周期、会话持久化、工具执行以及模型可见的提问工具仍由不同组合项负责。插件要求 stdin 和 stdout 均为 TTY;条件不满足时会失败,不会静默切换为逐行输出。 该包是终端入口,而不是完整应用。宿主在已配置 agent 之前挂载 `@deepseek-ai/dsh-tui`,并围绕它组合后端、工具和策略。产品 CLI 目前不交付终端组合;非交互任务使用 headless 模式,Web 是已安装产品中面向人的界面,而 ACP 仍是独立的自动化协议。 diff --git a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md index 2c8e92671f..9308df7fb4 100644 --- a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md +++ b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.md @@ -13,7 +13,7 @@ The TUI platform contract must follow the runtime shipped to users rather than t ## Decision -[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) supports interactive terminals on Windows as well as macOS and Linux. The product continues to use pi-tui's `ProcessTerminal`; on Windows it enables virtual-terminal input after raw mode and avoids the Unix-only `SIGWINCH` refresh. DeepSeek Harness adds no platform rejection or reduced Windows mode. +[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) supports interactive terminals on Windows as well as macOS and Linux. The product continues to use pi-tui's `ProcessTerminal`; on Windows it enables virtual-terminal input after raw mode and avoids the Unix-only `SIGWINCH` refresh. DeepSeek Harness adds no platform rejection or reduced Windows mode. The real Loader smoke selects a native pseudo-terminal boundary by host. macOS and Linux retain the Python POSIX PTY driver. Windows uses `node-pty` and ConPTY. Both drivers receive the same launch command, environment, terminal dimensions, marker-gated input actions, timeout, expected exit code, and output assertions, and all three smoke scenarios run on every supported platform. diff --git a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md index cb5d05da2b..8b4a1eb4ce 100644 --- a/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md +++ b/.agents/notes/archived/feature/2026-07-20-windows-tui-support.zh.md @@ -13,7 +13,7 @@ TUI 平台契约必须以交付给用户的运行时为准,而不是取决于 ## 决策 -[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 在 Windows、macOS 和 Linux 上均支持交互式终端。产品继续使用 pi-tui 的 `ProcessTerminal`;在 Windows 上,它会在进入原始模式后启用虚拟终端输入,并避开仅适用于 Unix 的 `SIGWINCH` 刷新。DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。 +[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 在 Windows、macOS 和 Linux 上均支持交互式终端。产品继续使用 pi-tui 的 `ProcessTerminal`;在 Windows 上,它会在进入原始模式后启用虚拟终端输入,并避开仅适用于 Unix 的 `SIGWINCH` 刷新。DeepSeek Harness 不增加平台拒绝逻辑,也不采用功能受限的 Windows 模式。 真实 Loader 冒烟测试根据宿主选择原生伪终端边界。macOS 和 Linux 继续使用 Python POSIX PTY 驱动,Windows 则使用 `node-pty` 和 ConPTY。两种驱动接收相同的启动命令、环境、终端尺寸、以标记为触发条件的输入动作、超时、预期退出码和输出断言;3 个冒烟场景都会在每个受支持平台上运行。 diff --git a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md index 038d12a561..e151f92d0d 100644 --- a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md +++ b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.md @@ -11,7 +11,7 @@ The [skill system](2026-07-05-skill-system.md) shipped with model-initiated load ## Decision -The [`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) front door owns a `/skill: [instructions]` command. On submit it loads the named skill and delivers one text block as a user turn — sent with `agent.send()` while idle and `agent.steer()` while running, the same rule as ordinary editor input. The block is `renderSkillInvocation(skill, instructions)`: a `` element wrapping the skill body, preceded by one resource-base line when the provider exposes one, with the user's trailing text appended after a blank line. The command is a TUI-only affordance; it adds no model-facing tool. Its visibility and loading policy comes from the shared [independent model and user skill invocation policy](2026-07-28-skill-invocation-policy.md). +The [`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) front door owns a `/skill: [instructions]` command. On submit it loads the named skill and delivers one text block as a user turn — sent with `agent.send()` while idle and `agent.steer()` while running, the same rule as ordinary editor input. The block is `renderSkillInvocation(skill, instructions)`: a `` element wrapping the skill body, preceded by one resource-base line when the provider exposes one, with the user's trailing text appended after a blank line. The command is a TUI-only affordance; it adds no model-facing tool. Its visibility and loading policy comes from the shared [independent model and user skill invocation policy](2026-07-28-skill-invocation-policy.md). The TUI reads the skill service through `ctx.get('skills')`, not a declared injection, because skills mount conditionally: a deployment without the registry keeps a working front door, and `/skill:` there reports that skills are unavailable rather than failing to mount. `createTuiChat` is synchronous while `ctx.skills.list()` is async, so autocomplete seeds the static slash commands immediately and rebuilds the provider with `skill:` entries once the catalog resolves; a resolution that arrives after disposal is dropped, and a rejected lookup keeps the base commands. diff --git a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md index af01642c07..ac1c4da948 100644 --- a/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md +++ b/.agents/notes/archived/feature/2026-07-21-tui-skill-slash-command.zh.md @@ -11,7 +11,7 @@ Archived: 2026-08-04 ## Decision -[`@deepseek-ai/dsh-tui`](../../../../packages/interaction/tui/README.md) 前门拥有一条 `/skill: [instructions]` 命令。提交时它加载指定的 skill,并投递一个文本块作为用户轮次——空闲时用 `agent.send()` 发送、运行中用 `agent.steer()` 中途引导,与普通编辑器输入遵循同一规则。该文本块由 `renderSkillInvocation(skill, instructions)` 生成:一个包裹 skill 正文的 `` 元素,当提供方暴露资源基址时在其前加一行资源基址行,用户尾随的文本在空行之后追加。该命令是 TUI 独有的功能;它不新增任何面向模型的工具。其可见性和加载策略来自共享的[模型与用户独立 skill 调用策略](2026-07-28-skill-invocation-policy.md)。 +[`@deepseek-ai/dsh-tui`](../../../../packages/ui/tui/README.md) 前门拥有一条 `/skill: [instructions]` 命令。提交时它加载指定的 skill,并投递一个文本块作为用户轮次——空闲时用 `agent.send()` 发送、运行中用 `agent.steer()` 中途引导,与普通编辑器输入遵循同一规则。该文本块由 `renderSkillInvocation(skill, instructions)` 生成:一个包裹 skill 正文的 `` 元素,当提供方暴露资源基址时在其前加一行资源基址行,用户尾随的文本在空行之后追加。该命令是 TUI 独有的功能;它不新增任何面向模型的工具。其可见性和加载策略来自共享的[模型与用户独立 skill 调用策略](2026-07-28-skill-invocation-policy.md)。 TUI 通过 `ctx.get('skills')` 读取 skill 服务,而非声明式注入,因为 skill 是条件挂载的:没有注册表的部署仍保有可用的前门,此时 `/skill:` 会报告 skill 不可用,而不是挂载失败。`createTuiChat` 是同步的,而 `ctx.skills.list()` 是异步的,所以自动补全先立即种入静态斜杠命令,待目录解析完成后再用 `skill:` 条目重建 provider(提供方);在 dispose(资源释放)之后才到达的解析结果会被丢弃,而被拒绝的查找会保留基础命令。 diff --git a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md index ff4a310f08..2ef926cff9 100644 --- a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md +++ b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.md @@ -13,7 +13,7 @@ The TUI assistant message opened with a single header line joining the `Assistan **Split the label from the timing; render the timing as the message's trailing line.** -`AssistantMessageComponent` (packages/interaction/tui/src/index.ts) now emits the bold `Assistant` label as the first line and appends the dim timing string (already assembled by `StreamingAssistantComponent.rebuild()` as `header`, including the `· Completed …` suffix when settled) as the last child, after reasoning and text. The timing content, bucket-hiding, and completion-time behavior are unchanged — only its position moved from the top to the bottom of the message. +`AssistantMessageComponent` (packages/ui/tui/src/index.ts) now emits the bold `Assistant` label as the first line and appends the dim timing string (already assembled by `StreamingAssistantComponent.rebuild()` as `header`, including the `· Completed …` suffix when settled) as the last child, after reasoning and text. The timing content, bucket-hiding, and completion-time behavior are unchanged — only its position moved from the top to the bottom of the message. ## Alternatives considered diff --git a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md index 08ced9603d..282ac1e65b 100644 --- a/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md +++ b/.agents/notes/archived/feature/2026-07-27-assistant-timing-header-trailing.zh.md @@ -13,7 +13,7 @@ TUI 的助手消息此前以一行开头,把 `Assistant` 标签和步骤计时 **把标签与计时拆开;计时作为消息的末行渲染。** -`AssistantMessageComponent`(packages/interaction/tui/src/index.ts)现在把加粗的 `Assistant` 标签作为首行,并把暗色的计时串(仍由 `StreamingAssistantComponent.rebuild()` 组装为 `header`,settled 时含 `· Completed …` 后缀)作为最后一个子节点,追加在 reasoning 与正文之后。计时内容、隐藏零值桶以及完成时间的行为均不变——仅位置从消息顶部移到底部。 +`AssistantMessageComponent`(packages/ui/tui/src/index.ts)现在把加粗的 `Assistant` 标签作为首行,并把暗色的计时串(仍由 `StreamingAssistantComponent.rebuild()` 组装为 `header`,settled 时含 `· Completed …` 后缀)作为最后一个子节点,追加在 reasoning 与正文之后。计时内容、隐藏零值桶以及完成时间的行为均不变——仅位置从消息顶部移到底部。 ## Alternatives considered diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml index a9e56019b3..8feded0ad4 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md -2026-07-28-dsh-guided-skill-session-commands.md: 606513919a927af3bb7e9b74544fd8df9a1682ce -2026-07-28-dsh-guided-skill-session-commands.zh.md: 39af4a3a554ba3053f25b3d034346d3e1f4ce237 +# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-dsh-guided-skill-session-commands.md +2026-07-28-dsh-guided-skill-session-commands.md: 454d090a55db8987f8e4987aba67deff1b21b1a0 +2026-07-28-dsh-guided-skill-session-commands.zh.md: 64dad4e39c6fd3e3341313c3f8dd2b04947695cf diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md index 606513919a..454d090a55 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.md @@ -25,7 +25,7 @@ The `dsh-migrate` skill is bundled under `skills/` (shipped through `DSH_BUNDLED `apps/cli/tests/args.spec.ts` gains routing for `migrate`/`upgrade` (bare discriminant) and exit-1 for every leaked option on either side of each subcommand. -`packages/interaction/tui/tests/tui.spec.ts` gains two fake-terminal cases in the existing skill describe block: `config.initialSkill` set delivers the rendered skill body as the first turn with no user input, and an unknown initial skill reports a notice without sending. `runSkillSession` itself is composition inside the module's `v8 ignore` block, like `runTui`/`runMeta`. +`packages/ui/tui/tests/tui.spec.ts` gains two fake-terminal cases in the existing skill describe block: `config.initialSkill` set delivers the rendered skill body as the first turn with no user input, and an unknown initial skill reports a notice without sending. `runSkillSession` itself is composition inside the module's `v8 ignore` block, like `runTui`/`runMeta`. No keyless PTY snapshot: per the maintainer's scope call for this change, unit coverage plus interactive verification suffices, and the seed rides the already-snapshotted `/skill:` render path. Both commands were verified interactively in tmux from a scratch cwd: `dsh migrate` loaded `dsh-migrate` and asked which source agent; `dsh upgrade` loaded `dsh-upgrade`, which pulled in `dsh-customize` and began checkout discovery. diff --git a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md index 39af4a3a55..64dad4e39c 100644 --- a/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md +++ b/.agents/notes/archived/feature/2026-07-28-dsh-guided-skill-session-commands.zh.md @@ -25,7 +25,7 @@ Archived: 2026-08-03 `apps/cli/tests/args.spec.ts` 新增 `migrate`/`upgrade` 的路由(裸判别式),以及每个子命令两侧任一泄漏选项的退出码 1。 -`packages/interaction/tui/tests/tui.spec.ts` 在既有 skill describe 块中新增两个伪终端用例:设置 `config.initialSkill` 时无需用户输入即把渲染后的 skill 正文作为首轮投递;未知的初始 skill 以通知形式报告且不发送。`runSkillSession` 本身是模块 `v8 ignore` 块内的组装,与 `runTui`/`runMeta` 相同。 +`packages/ui/tui/tests/tui.spec.ts` 在既有 skill describe 块中新增两个伪终端用例:设置 `config.initialSkill` 时无需用户输入即把渲染后的 skill 正文作为首轮投递;未知的初始 skill 以通知形式报告且不发送。`runSkillSession` 本身是模块 `v8 ignore` 块内的组装,与 `runTui`/`runMeta` 相同。 无 keyless PTY 快照:依据维护者对本次改动的范围裁定,单元覆盖加交互式验证已足够,且播种走的是已有快照的 `/skill:` 渲染路径。两个命令均已在 tmux 中从临时 cwd 交互式验证:`dsh migrate` 加载 `dsh-migrate` 并询问源 agent;`dsh upgrade` 加载 `dsh-upgrade`,后者引入 `dsh-customize` 并开始 checkout 发现。 diff --git a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md index 76cc213ba8..ca5edc50bf 100644 --- a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md +++ b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.md @@ -11,7 +11,7 @@ Roughly ten e2e/smoke files re-derived the same spawn-collect-timeout choreograp Two related test-infra hand-rolls compounded the case: -- `packages/support/llm-mock-server/src/cli.ts` hand-tokenized 17 value-taking `--flag value` options plus boolean flags (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/scaffold/scripts`). +- `packages/support/llm-mock-server/src/cli.ts` hand-tokenized 17 value-taking `--flag value` options plus boolean flags (~45–60 lines of loop and value-extraction helpers) where the `node:util` `parseArgs` builtin is already the repo idiom (`cli-demo`, `acp-demo`, `verify-runtime-closure.ts`, `packages/sdk/scripts`). - `apps/web/tests/smoke-real.e2e.ts` and `apps/web/tests/scaffold.ts` carried two verbatim copies of a regex `.env` parser (~20 lines) where the `process.loadEnvFile` builtin has exactly the required no-override semantics — and the vitest e2e/snapshot/web configs already load root `.env` with it before these files run, making the copies dead. - The snapshot harness hand-rolled three poll-until-deadline loops (`waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile` in `packages/support/acp-snapshot/src/harness.ts`, ~55 lines) plus `waitForFile` in `crash-recovery.e2e.ts`, where `vi.waitFor`/`expect.poll` cover the shape — vitest is already a runtime dependency of `dsh-acp-snapshot`, so this adds nothing. diff --git a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md index f597fd1e08..186e5cd560 100644 --- a/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md +++ b/.agents/notes/archived/testing/2026-07-26-execa-for-test-subprocess-plumbing.zh.md @@ -11,7 +11,7 @@ Archived: 2026-08-07 另有两处相关的测试基础设施手写代码进一步强化了替换的理由: -- `packages/support/llm-mock-server/src/cli.ts` 曾手工逐个切分 17 个带值的 `--flag value` 选项外加若干布尔标志(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/scaffold/scripts`)。 +- `packages/support/llm-mock-server/src/cli.ts` 曾手工逐个切分 17 个带值的 `--flag value` 选项外加若干布尔标志(约 45–60 行的循环与取值辅助函数),而 `node:util` 内置的 `parseArgs` 早已是本仓库的惯用写法(`cli-demo`、`acp-demo`、`verify-runtime-closure.ts`、`packages/sdk/scripts`)。 - `apps/web/tests/smoke-real.e2e.ts` 与 `apps/web/tests/scaffold.ts` 曾携带两份逐字相同的正则 `.env` 解析器拷贝(约 20 行),而内置的 `process.loadEnvFile` 恰好具备所需的「不覆盖已有值」语义;并且 vitest 的 e2e/snapshot/web 配置在这些文件运行之前就已用它加载了根 `.env`,这两份拷贝实为死代码。 - 快照 harness 曾手写三个「轮询直到截止时间」的循环(`packages/support/acp-snapshot/src/harness.ts` 中的 `waitForPersistedTurnStart`/`waitForPersistedTurnEnd`/`waitForWorkspaceFile`,约 55 行),外加 `crash-recovery.e2e.ts` 中的 `waitForFile`,而 `vi.waitFor`/`expect.poll` 正好覆盖这种形态;vitest 本来就是 `dsh-acp-snapshot` 的运行时依赖,因此这不新增任何东西。 diff --git a/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml new file mode 100644 index 0000000000..e597017076 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-29-package-regrouping.md +2026-07-29-package-regrouping.md: bb9f7deb775a0922de8b20f766446ebf007718d3 +2026-07-29-package-regrouping.zh.md: 1d7d79e0cfad95382c036b3c99712775870aa48a diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md similarity index 56% rename from .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md rename to .agents/notes/implemented/architecture/2026-07-29-package-regrouping.md index 9a56f9f5f1..bb9f7deb77 100644 --- a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md +++ b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md @@ -1,24 +1,24 @@ # Agent Note: Regroup packages/ by measured clustering -Status: proposed +Status: implemented English | [中文](2026-07-29-package-regrouping.zh.md) ## Problem -The two-level `packages//` hierarchy ([original decision](../../archived/architecture/2026-06-20-package-hierarchy.md)) has drifted since June: 167 packages now sit in 40 groups, and several group boundaries no longer match how the packages actually cluster. +The two-level `packages//` hierarchy ([original decision](../../archived/architecture/2026-06-20-package-hierarchy.md)) had drifted since June: 167 packages sat in 42 groups, and several group boundaries no longer matched how the packages actually cluster. -- `ui/` mixes four unrelated planes: the human terminal channel (`tui`), the SDK's JSON-RPC server half (`jsonrpc`, whose peer dependency on `dsh-sdk-protocol` binds it to the `sdk/` stack), the human-interaction seams (`user-interaction`, `user-approval`, `permission`, `tool-ask-user`, `commands`), and channel-neutral boot glue (`app-boot`). Its own README narrates the mixture instead of stating a role. -- The session family is fragmented across five groups — `session-persistence/`, `session-projection/`, `session-query/`, `session-title/`, and `telemetry/` — although the measured dependency edges tie them together (query → persistence, title → projection, projection → persistence; see [docs/module-graph.md](../../../../docs/module-graph.md)). -- Two group names collide with unrelated packages: `telemetry/` (session reporting) vs `sdk/telemetry` = `dsh-telemetry` (launcher-side SDK telemetry), and `timeout/` (a tool-call guard) vs `util/timeout` (the generic promise utility). -- `cordis/` names its group after the framework every package is built on, so the name discriminates nothing; its single package `tool-cordis` is the runtime self-modification toolset. -- `sdk/` folder names are inconsistent: `sdk/sdk-client` and `sdk/sdk-protocol` repeat the group name while `sdk/telemetry`, `sdk/helper`, and `sdk/scripts` do not. +- `ui/` mixed four unrelated planes: the human terminal channel (`tui`), the SDK's JSON-RPC server half (`jsonrpc`, whose peer dependency on `dsh-sdk-protocol` binds it to the SDK wire stack), the human-interaction seams (`user-interaction`, `user-approval`, `permission`, `tool-ask-user`, `commands`), and channel-neutral boot glue (`app-boot`). Its own README narrated the mixture instead of stating a role. +- The session family was fragmented across five groups — `session-persistence/`, `session-projection/`, `session-query/`, `session-title/`, and `telemetry/` — although the measured dependency edges tie them together (query → persistence, title → projection, projection → persistence; see [docs/module-graph.md](../../../../docs/module-graph.md)). +- Two group names collided with unrelated packages: `telemetry/` (session reporting) vs `dsh-telemetry` (launcher-side SDK telemetry), and `timeout/` (a tool-call guard) vs `util/timeout` (the generic promise utility). +- `cordis/` named its group after the framework every package is built on, so the name discriminated nothing; its single package `tool-cordis` is the runtime self-modification toolset. +- The old `sdk/` folder names were inconsistent: `sdk/sdk-client` and `sdk/sdk-protocol` repeated the group name while `sdk/telemetry`, `sdk/helper`, and `sdk/scripts` did not. The north star for the regrouping: **closely clustered packages share a group.** A cluster is measured — peer-dependency edges and co-change — not thematic. An isolated seam family may stand alone as a small group; the failure mode to avoid is the grab-bag whose name describes no single role. -## Proposal +## Decision -Recompose six groups; every other group keeps today's boundary and contents (the dependency analysis confirmed the capability families — `bash/`, `pty/`, `code-runtime/`, `sandbox/`, `subprocess/`, `fs/`, `lsp/`, `web/`, `skill/`, and the rest — were already drawn correctly). npm package names do not change in this reorganization; the folder tree carries the whole change. +Six groups are recomposed; every other group keeps its prior boundary and contents (the dependency analysis confirmed the capability families — `bash/`, `pty/`, `code-runtime/`, `sandbox/`, `subprocess/`, `fs/`, `lsp/`, `web/`, `skill/`, and the rest — were already drawn correctly). npm package names did not change; the folder tree carries the whole change. | Group | Members (folder names) | From | |---|---|---| @@ -29,18 +29,18 @@ Recompose six groups; every other group keeps today's boundary and contents (the | `guard/` | repeat-tool-guard, timeout-policy | `guard/` + `timeout/` | | `self-modification/` | tool-cordis | `cordis/` | -- **`session/`** is the durable session data plane: the persistence seam with its backends and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (review: prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals. Absorbing `telemetry/` ends the group-name collision with `dsh-telemetry`. +- **`session/`** is the durable session data plane: the persistence seam with its backends and checkpoint policy, the projection fold that serves whole values from that log, log-backed titles, and OTel reporting. The title fold is itself load-bearing for the read side (`session-query` peer-depends on `dsh-session-title`), so titles belong with the data plane, not in a derived-services annex. The plain name is deliberate (review: prefer names a human would say); the nearby `core/session` package remains the live in-memory service, while this group is the durable family around it. `session-query/` stays a standalone group — the read/tool surface has its own model tools and SQLite FTS backend and is consumed independently of persistence internals. Absorbing `telemetry/` ended the group-name collision with `dsh-telemetry`. - **`interaction/`** is the human-collaboration plane plus the terminal channel that answers it: the question/approval seams, the permission preset, the model-facing `ask_user_question` tool, the human-command registry (`plan-mode` and `command-goal` already consume `commands` together with the interaction seams), and `tui` — the interactive channel is the plane's richest provider and consumer (peer edges to `commands` and `user-interaction`), and a one-package `tui/` group would spend a top-level name on one plugin. - **`boot/`** is a role-complete single-package group: the shared bin boot glue that belongs to no channel and no assembly (consumed by `apps/cli`, the `scaffold/` launcher, and the `examples/` demo bins). -- **`scaffold/`** is the developer-tooling family: project helper, launcher, initializer, wire protocol with both ends (`server` is today's `ui/jsonrpc`), and launcher telemetry. Renamed from `sdk/` in review: the whole `packages/` tree *is* the SDK, so a group named `sdk/` inside it says nothing; `scaffold/` names the create/launch/drive-a-project role. Folders drop the legacy `sdk-` prefix (`protocol`, `client`, `server`), matching the `client/`/`host/` role-named folder style. +- **`scaffold/`** is the developer-tooling family: project helper, launcher, initializer, wire protocol with both ends (`server` is the former `ui/jsonrpc`), and launcher telemetry. Renamed from `sdk/` in review: the whole `packages/` tree *is* the SDK, so a group named `sdk/` inside it said nothing; `scaffold/` names the create/launch/drive-a-project role. Folders drop the legacy `sdk-` prefix (`protocol`, `client`, `server`), matching the `client/`/`host/` role-named folder style; the three affected npm names are mapped explicitly beside the group wildcard in `tsconfig.base.json` until the deferred renames land. - **`guard/`** keeps its documented role, loop-hygiene guards, and gains the tool-call timeout enforcer, dissolving the one-package `timeout/` group whose name collided with `util/timeout`. - **`self-modification/`** names the role `cordis/` obscured: the toolset with which the agent inspects and mounts plugins in its own live runtime, and the landing zone for future self-modification packages. -40 groups become 37; the win is clustering correctness and truthful names, not count. +42 groups became 39; the win is clustering correctness and truthful names, not count. ## Deferred renames (FIXME markers) -Five npm names should eventually change, but renaming inside the reorganization would turn a pure-move PR into an import-churn PR. Instead, the implementation adds a `FIXME` to each affected package's module JSDoc naming the intended new name. `FIXME` blocks a tagged release ([marker semantics](../../../../docs/development.md)), which is the wanted forcing function: these renames are only free while nothing external consumes the packages. +Five npm names should eventually change, but renaming inside the reorganization would have turned a pure-move PR into an import-churn PR. Instead, each affected package's module JSDoc carries a `FIXME` naming the intended new name. `FIXME` blocks a tagged release ([marker semantics](../../../../docs/development.md)), which is the wanted forcing function: these renames are only free while nothing external consumes the packages. | Current npm name | Intended name | Why | |---|---|---| @@ -52,19 +52,17 @@ Five npm names should eventually change, but renaming inside the reorganization The first four are settled intent; resolving them converges the SDK wire stack's npm names on `dsh-sdk-*` (the npm prefix names the product stack; the `scaffold/` folder names the role). `@deepseek-ai/create-sdk` keeps its documented npm-initializer exception. -## Implementation +## What the move touched -One mechanical PR, landed in a quiet window: a single conflict event for the many open PRs, an atomic documentation update, and pure-move reviewability (`git mv` + path fixes, rename detection intact). If review prefers, the same content splits into a three-PR stack (the session fold; `ui/` dissolution + `scaffold/`; `self-modification/` + `guard/` merge), at the cost of three rebase events for everyone else. +The moves landed as `git mv` commits, one per region (the session fold; `ui/` dissolution + `scaffold/`; `guard/` merge + `self-modification/`), so rename detection carries review. A group move touched: the moved package's `tsconfig.json` relative `references` and every dependent's entry (including the `apps/cli` project references), the tsconfig aggregate and path maps, group READMEs (five new bilingual triplets, deletions for dissolved groups, the [packages/README.md](../../../../packages/README.md) hierarchy table, the root `AGENTS.md` layout map), regenerated artifacts (`docs/module-graph.md`, path-embedding catalogs, the lockfile's importer keys), and root-relative `packages/...` citations in prose and gate scripts. Remaining group-path referents (workspace configs, test globs, lint keys) were found mechanically by the acceptance gates failing loud — the repository's own misconfiguration rule. -A group move touches: the moved package's `tsconfig.json` relative `references` and every dependent's entry (including the `apps/cli` project references), the tsconfig aggregate and path maps, group READMEs (five new bilingual triplets, deletions for dissolved groups, the [packages/README.md](../../../../packages/README.md) hierarchy table, the root `AGENTS.md` layout map), regenerated artifacts (`docs/module-graph.md`, path-embedding catalogs, the lockfile's importer keys), and root-relative `packages/...` citations in prose (root and `apps/cli` READMEs, 7 script files + 22 docs files measured today; `verify-package-paths` gates the Markdown/TypeScript classes of stragglers). This list conveys the shape of the change, not a file inventory: every remaining group-path referent (workspace configs, test globs, lint keys) is found mechanically at implementation time by the acceptance gates failing loud, which is the repository's own misconfiguration rule. +A group move did not touch: npm names, imports, `cordis.yml` configs, snapshot fixtures, the `pnpm-workspace.yaml`/`tsdown` globs (both `packages/*/*`), or the Python runtime manifest — all reference packages by npm name. Each FIXME landed in the same commit as the move that created its context. -A group move does not touch: npm names, imports, `cordis.yml` configs, snapshot fixtures, the `pnpm-workspace.yaml`/`tsdown` globs (both `packages/*/*`), or the Python runtime manifest — all reference packages by npm name (verified by grep before this proposal). Each FIXME lands in the same PR as the move that creates its context. - -`client/` and `host/` are out of scope and unchanged. The in-flight `experimental/` group proposal (PR #844) is orthogonal — a release-boundary container, not a clustering decision; the only interaction is a trivial merge in the packages/README.md table. +`client/` and `host/` were out of scope and are unchanged. The `experimental/` group proposal (PR #844) is orthogonal — a release-boundary container, not a clustering decision; the only interaction is a trivial merge in the packages/README.md table. ## Alternatives considered -**Coarse domain buckets** (`exec/` = subprocess+sandbox+bash+pty+code-runtime, `workspace/` = fs+lsp+workspace, `orchestration/` = subagent+workflow+tasks, `knowledge/` = web+skill, `collab/` = plan+todo+goal; ~16 groups). Rejected: the measured graph contradicts the merges. `sandbox` and `subprocess` are shared infrastructure consumed across families (bash ×5, fs ×5, pty, lsp, mcp, subagent, sdk edges), `web` ↔ `skill` have zero edges, and a large bucket reproduces the `ui/` grab-bag at a larger scale. +**Coarse domain buckets** (`exec/` = subprocess+sandbox+bash+pty+code-runtime, `workspace/` = fs+lsp+workspace, `orchestration/` = subagent+workflow+tasks, `knowledge/` = web+skill, `collab/` = plan+todo+goal; ~16 groups). Rejected: the measured graph contradicts the merges. `sandbox` and `subprocess` are shared infrastructure consumed across families (bash ×5, fs ×5, pty, lsp, mcp, subagent, scaffold edges), `web` ↔ `skill` have zero edges, and a large bucket reproduces the `ui/` grab-bag at a larger scale. **Abstract layer names** (`capability/`, `policy/`, `extension/`, `provider/`). Rejected: they describe every plugin equally badly, and a `capability/` bucket would hold ~50 packages. @@ -88,19 +86,11 @@ A group move does not touch: npm names, imports, `cordis.yml` configs, snapshot **Renaming `context/` to `request-context/`.** Rejected: within this tree the group is unambiguous in situ; the churn is unjustified. -## Acceptance criteria +## Consequences -- The tree matches the map: the six recomposed groups hold exactly the listed members; the groups `ui/`, `sdk/`, `telemetry/`, `timeout/`, `cordis/`, `session-persistence/`, `session-projection/`, and `session-title/` no longer exist; every other group's contents are unchanged. -- Zero npm renames: the workspace package-name set is identical before and after. -- The five FIXME markers are present, each naming its intended new name. -- New and updated group READMEs form complete bilingual triplets; the packages/README.md hierarchy table matches the tree; docs/module-graph.md is regenerated. -- `pnpm run typecheck`, `pnpm run test`, `pnpm run doc-sync`, and `pnpm run hygiene` pass, and the unit/snapshot suites collect the same test files before and after the move (group-scoped globs in test configs must not silently match nothing). - -## Risks - -- **Open-PR conflicts.** Every open PR touching a moved file must rebase across the move. Mitigated by landing as one PR in a quiet window, publishing the mapping table in the PR body, and the moves being pure renames (rename detection resolves most hunks mechanically). -- **Reference drift.** Docs or scripts citing old paths can go stale; `verify-package-paths` exists precisely for moved-package drift and runs in `doc-sync`. -- **Translation load.** Five new group README triplets plus edits to existing pairs land in the implementation PR. -- **Deferred renames are release blockers by design.** A FIXME that later proves wrong must be removed explicitly with rationale, never silently dropped. -- **Single-package groups remain** (`boot/`, `self-modification/`, and existing ones such as `acp/`). Accepted deliberately: each is role-complete rather than a fragment of a family, and a truthful small group beats a nominal merge. -- **What this gives up:** nothing functional — the change is navigational. Muscle memory and external links to old GitHub paths break, which is acceptable pre-release with no external consumers. +- The tree matches the map: the six recomposed groups hold exactly the listed members; the groups `ui/`, `sdk/`, `telemetry/`, `timeout/`, `cordis/`, `session-persistence/`, `session-projection/`, and `session-title/` no longer exist; every other group's contents are unchanged. The workspace package-name set is identical before and after (zero npm renames), and the five FIXME markers pin the deferred ones. A FIXME that later proves wrong must be removed explicitly with rationale, never silently dropped. +- What pins the result: `pnpm run typecheck`, the unit suites of every moved group, `verify-package-paths`, `verify-md-links`, and the corpus-wide translation pairing all pass on the moved tree; the group-scoped test globs in `vitest.snapshot.config.ts` were rewritten with the moves so the suites collect the same test files as before (a fail-open glob would silently drop coverage). +- Every open PR touching a moved file rebases across the move once; the mapping table in the PR body and rename detection resolve most hunks mechanically. +- Single-package groups remain (`boot/`, `self-modification/`, and existing ones such as `acp/`). Accepted deliberately: each is role-complete rather than a fragment of a family, and a truthful small group beats a nominal merge. +- The `scaffold/` folders diverge from their npm names until the deferred renames land — the one transitional asymmetry, carried by three explicit `paths` entries in `tsconfig.base.json` and resolved by the FIXME renames. +- What this gave up: nothing functional — the change is navigational. Muscle memory and external links to old GitHub paths break, which is acceptable pre-release with no external consumers. diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md similarity index 66% rename from .agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md rename to .agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md index c49c5a1daf..1d7d79e0cf 100644 --- a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-29-package-regrouping.zh.md @@ -1,24 +1,24 @@ # Agent Note: 按实测聚类重新划分 packages/ 分组 -Status: proposed +Status: implemented [English](2026-07-29-package-regrouping.md) | 中文 ## Problem -两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.md))自 6 月以来已经漂移:167 个包(package)如今坐落在 40 个组里,若干组边界已经对不上这些包的实际聚类。 +两级 `packages//` 层级结构([原始决策](../../archived/architecture/2026-06-20-package-hierarchy.md))自 6 月以来已经漂移:167 个包(package)彼时坐落在 42 个组里,若干组边界已经对不上这些包的实际聚类。 -- `ui/` 混杂了四个互不相关的平面:人类终端通道(`tui`)、SDK 的 JSON-RPC 服务端一半(`jsonrpc`,它对 `dsh-sdk-protocol` 的对等依赖(peer dependency)把它绑在 `sdk/` 这一栈上)、人机交互 seam(`user-interaction`、`user-approval`、`permission`、`tool-ask-user`、`commands`),以及与通道无关的 boot 胶水(`app-boot`)。它自己的 README 只能逐一叙述这堆混杂,说不出一个统一职责。 +- `ui/` 混杂了四个互不相关的平面:人类终端通道(`tui`)、SDK 的 JSON-RPC 服务端一半(`jsonrpc`,它对 `dsh-sdk-protocol` 的对等依赖(peer dependency)把它绑在 SDK 通信栈上)、人机交互 seam(`user-interaction`、`user-approval`、`permission`、`tool-ask-user`、`commands`),以及与通道无关的 boot 胶水(`app-boot`)。它自己的 README 只能逐一叙述这堆混杂,说不出一个统一职责。 - 会话家族被割裂在五个组里——`session-persistence/`、`session-projection/`、`session-query/`、`session-title/` 与 `telemetry/`——而实测依赖边明明把它们连成一体(query → persistence、title → projection、projection → persistence;见 [docs/module-graph.md](../../../../docs/module-graph.md))。 -- 两个组名与不相干的包撞名:`telemetry/`(会话上报)撞上 `sdk/telemetry` = `dsh-telemetry`(启动器侧 SDK telemetry),`timeout/`(一个工具调用守卫)撞上 `util/timeout`(通用 promise 工具)。 +- 两个组名与不相干的包撞名:`telemetry/`(会话上报)撞上 `dsh-telemetry`(启动器侧 SDK telemetry),`timeout/`(一个工具调用守卫)撞上 `util/timeout`(通用 promise 工具)。 - `cordis/` 拿所有包共同依托的框架给自己的组命名,这个名字因此毫无区分度;组里唯一的包 `tool-cordis` 是运行时自我修改工具集。 -- `sdk/` 的目录命名不一致:`sdk/sdk-client` 和 `sdk/sdk-protocol` 重复了组名,而 `sdk/telemetry`、`sdk/helper`、`sdk/scripts` 没有。 +- 旧 `sdk/` 的目录命名不一致:`sdk/sdk-client` 和 `sdk/sdk-protocol` 重复了组名,而 `sdk/telemetry`、`sdk/helper`、`sdk/scripts` 没有。 这次重新分组的指导准则:**聚类紧密的包同处一组。**聚类以实测为准(对等依赖边与 co-change),而非按主题归类。孤立的 seam 家族可以自成一个小组;要避免的失败形态,是名字概括不出单一职责的大杂烩组。 -## Proposal +## Decision -重组六个组;其余每个组都保持今天的边界与内容不变(依赖分析确认各能力家族——`bash/`、`pty/`、`code-runtime/`、`sandbox/`、`subprocess/`、`fs/`、`lsp/`、`web/`、`skill/` 及其余——本来就划得正确)。这次重组不改任何 npm 包名;整个变更全部由目录树承载。 +重组六个组;其余每个组都保持先前的边界与内容不变(依赖分析确认各能力家族——`bash/`、`pty/`、`code-runtime/`、`sandbox/`、`subprocess/`、`fs/`、`lsp/`、`web/`、`skill/` 及其余——本来就划得正确)。npm 包名一个未改;整个变更全部由目录树承载。 | 组 | 成员(目录名) | 来源 | |---|---|---| @@ -32,15 +32,15 @@ Status: proposed - **`session/`** 是持久会话数据平面:持久化 seam 连同其各后端与检查点策略、从该日志折叠(fold)出全量值对外供值的投影、日志兜底的标题,以及 OTel 上报。标题折叠本身就是读取侧的承重构件(`session-query` 对 `dsh-session-title` 声明对等依赖),所以标题属于数据平面,而非某个「派生服务」附属区。用这个朴素的名字是有意为之(评审意见:名字要像人起的);旁边的 `core/session` 包仍是常驻内存的实时服务,本组则是围绕它的持久家族。`session-query/` 保持独立成组:这个读取/工具面自带模型工具和 SQLite FTS 后端,其消费不依赖持久化内部实现。吸收 `telemetry/` 之后,与 `dsh-telemetry` 的组名冲突就此终结。 - **`interaction/`** 是人机协作平面加上应答它的终端通道:提问/批准 seam、权限预设、面向模型的 `ask_user_question` 工具、人类命令注册表(`plan-mode` 与 `command-goal` 已经把 `commands` 和各交互 seam 放在一起消费),以及 `tui`——这个交互通道是该平面最重的提供方与消费方(对 `commands` 与 `user-interaction` 均有对等依赖边),而一个单包 `tui/` 组会把一个顶层名字花在一个插件上。 - **`boot/`** 是角色完备的单包组:不归属任何通道也不归属任何组装的共享 bin boot 胶水(被 `apps/cli`、`scaffold/` 的启动器和 `examples/` 各演示 bin 消费)。 -- **`scaffold/`** 是开发者工具家族:项目 helper、启动器、初始化器、连同两端的通信协议(`server` 即今天的 `ui/jsonrpc`),以及启动器侧 telemetry。评审中从 `sdk/` 改名:整个 `packages/` 树本身就是 SDK,树里再放一个叫 `sdk/` 的组等于什么都没说;`scaffold/` 说出了「创建/启动/驱动项目」这一实际角色。目录去掉遗留的 `sdk-` 前缀(`protocol`、`client`、`server`),与 `client/`/`host/` 的角色命名风格一致。 +- **`scaffold/`** 是开发者工具家族:项目 helper、启动器、初始化器、连同两端的通信协议(`server` 即原先的 `ui/jsonrpc`),以及启动器侧 telemetry。评审中从 `sdk/` 改名:整个 `packages/` 树本身就是 SDK,树里再放一个叫 `sdk/` 的组等于什么都没说;`scaffold/` 说出了「创建/启动/驱动项目」这一实际角色。目录去掉遗留的 `sdk-` 前缀(`protocol`、`client`、`server`),与 `client/`/`host/` 的角色命名风格一致;在推迟的改名落地之前,受影响的三个 npm 名在 `tsconfig.base.json` 里于组通配符旁显式映射。 - **`guard/`** 保留其文档记载的角色(循环卫生守卫),并新纳入强制执行工具调用超时的包;那个与 `util/timeout` 撞名的单包组 `timeout/` 随之解散。 - **`self-modification/`** 把 `cordis/` 遮蔽掉的角色说了出来:它是 agent(智能体)检查并挂载自身实时运行时中插件所用的工具集,也是未来自我修改类包的落点。 -40 个组变为 37 个;收益在聚类正确与名实相符,不在数量增减。 +42 个组变为 39 个;收益在聚类正确与名实相符,不在数量增减。 ## Deferred renames (FIXME markers) -五个 npm 名最终应当改掉,但在这次重组内部改名,会把一个纯移动的 PR(Pull Request)变成大量翻改 import 的 PR。实现改为在每个受影响包的模块 JSDoc 里加一条 `FIXME`,写明意图中的新名字。`FIXME` 会阻塞打 tag 的发布([标记语义](../../../../docs/development.md)),这正是想要的倒逼机制:只有趁还没有外部消费方使用这些包时,这些改名才是零成本的。 +五个 npm 名最终应当改掉,但在这次重组内部改名,会把一个纯移动的 PR(Pull Request)变成大量翻改 import 的 PR。因此每个受影响包的模块 JSDoc 里带有一条 `FIXME`,写明意图中的新名字。`FIXME` 会阻塞打 tag 的发布([标记语义](../../../../docs/development.md)),这正是想要的倒逼机制:只有趁还没有外部消费方使用这些包时,这些改名才是零成本的。 | 当前 npm 名 | 目标名 | 原因 | |---|---|---| @@ -52,19 +52,17 @@ Status: proposed 前四个是已定的意图;兑现之后,SDK 通信栈的 npm 名随之收敛为 `dsh-sdk-*`(npm 前缀指产品栈,`scaffold/` 目录名指角色)。`@deepseek-ai/create-sdk` 保留其文档记载的 npm 初始化器特例。 -## Implementation +## What the move touched -整个变更作为一个机械化 PR,在低活动窗口期合入:众多开放中的 PR 只经历一次冲突事件,文档得到一次原子更新,评审享有纯移动的可评审性(`git mv` 加路径修正,重命名检测完好)。若评审偏好拆分,同样的内容也可以拆成三个 PR 的堆叠(会话折叠;`ui/` 解散加 `scaffold/`;`self-modification/` 加 `guard/` 合并),代价是其他所有人要经历三次变基事件。 +移动以 `git mv` 提交落地,每个区域一个提交(会话折叠;`ui/` 解散加 `scaffold/`;`guard/` 合并加 `self-modification/`),评审由重命名检测承载。组移动触及了:被移动包的 `tsconfig.json` 相对 `references` 及每个依赖方的对应条目(含 `apps/cli` 的 project references);tsconfig 聚合与路径映射;各组 README(五组新的双语三文件配对、被解散组的 README 删除、[packages/README.md](../../../../packages/README.md) 的层级结构表、根 `AGENTS.md` 的布局图);重新生成的产物(`docs/module-graph.md`、内嵌路径的目录、锁文件的 importer 键);以及散文与门禁脚本中以仓库根为基准的 `packages/...` 引用。其余每一处组路径引用(workspace 配置、测试 glob、lint 键)都由验收门禁的响亮失败机械地找了出来——这正是本仓库自己的「配置错误必须响亮失败」规则。 -组移动会触及:被移动包的 `tsconfig.json` 相对 `references` 及每个依赖方的对应条目(含 `apps/cli` 的 project references);tsconfig 聚合与路径映射;各组 README(五组新的双语三文件配对、被解散组的 README 删除、[packages/README.md](../../../../packages/README.md) 的层级结构表、根 `AGENTS.md` 的布局图);重新生成的产物(`docs/module-graph.md`、内嵌路径的目录、锁文件的 importer 键);以及散文中以仓库根为基准的 `packages/...` 引用(根 README 与 `apps/cli` README、今天实测 7 个脚本文件加 22 个文档文件;`verify-package-paths` 兜住 Markdown/TypeScript 这两类漏网引用)。这份清单陈述的是变更的形态,不是文件清点:其余每一处组路径引用(workspace 配置、测试 glob、lint 键)都在实现时由验收门禁的响亮失败机械地找出来——这正是本仓库自己的「配置错误必须响亮失败」规则。 +组移动未触及:npm 包名、import、`cordis.yml` 配置、快照 fixture(测试前置数据)、`pnpm-workspace.yaml` 与 `tsdown` 的 glob(都是 `packages/*/*`),以及 Python 运行时 manifest(元数据清单)——它们全部按 npm 包名引用包。每条 FIXME 都与创造其上下文的那次移动落在同一个提交。 -组移动不触及:npm 包名、import、`cordis.yml` 配置、快照 fixture(测试前置数据)、`pnpm-workspace.yaml` 与 `tsdown` 的 glob(都是 `packages/*/*`),以及 Python 运行时 manifest(元数据清单)——它们全部按 npm 包名引用包(提案之前已用 grep 核实)。每条 FIXME 都与创造其上下文的那次移动落在同一个 PR。 - -`client/` 与 `host/` 不在本次范围内,保持不变。在途的 `experimental/` 组提案(PR #844)与本案正交:它是发布边界容器,不是聚类决策;两者唯一的交集是 packages/README.md 表格里一次很小的合并。 +`client/` 与 `host/` 不在本次范围内,保持不变。`experimental/` 组提案(PR #844)与本案正交:它是发布边界容器,不是聚类决策;两者唯一的交集是 packages/README.md 表格里一次很小的合并。 ## Alternatives considered -**粗粒度领域桶**(`exec/` = subprocess+sandbox+bash+pty+code-runtime,`workspace/` = fs+lsp+workspace,`orchestration/` = subagent+workflow+tasks,`knowledge/` = web+skill,`collab/` = plan+todo+goal;约 16 个组)。不予采纳:实测依赖图与这些合并相矛盾。`sandbox` 和 `subprocess` 是被各家族跨界消费的共享基础设施(与 bash ×5、fs ×5、pty、lsp、mcp、subagent、sdk 均有依赖边),`web` ↔ `skill` 之间零依赖边,而大桶只会在更大尺度上复现 `ui/` 式大杂烩。 +**粗粒度领域桶**(`exec/` = subprocess+sandbox+bash+pty+code-runtime,`workspace/` = fs+lsp+workspace,`orchestration/` = subagent+workflow+tasks,`knowledge/` = web+skill,`collab/` = plan+todo+goal;约 16 个组)。不予采纳:实测依赖图与这些合并相矛盾。`sandbox` 和 `subprocess` 是被各家族跨界消费的共享基础设施(与 bash ×5、fs ×5、pty、lsp、mcp、subagent、scaffold 均有依赖边),`web` ↔ `skill` 之间零依赖边,而大桶只会在更大尺度上复现 `ui/` 式大杂烩。 **抽象分层名**(`capability/`、`policy/`、`extension/`、`provider/`)。不予采纳:这些名字对每个插件都同样地不达意,而且一个 `capability/` 桶会装下约 50 个包。 @@ -88,19 +86,11 @@ Status: proposed **把 `context/` 改名为 `request-context/`。** 不予采纳:在这棵树里,该组就地看并无歧义;这份改动开销并不值得。 -## Acceptance criteria +## Consequences -- 目录树与映射表一致:重组的六个组恰好持有所列成员;`ui/`、`sdk/`、`telemetry/`、`timeout/`、`cordis/`、`session-persistence/`、`session-projection/`、`session-title/` 这些组不复存在;其余每个组的内容不变。 -- npm 改名为零:workspace 的包名集合在前后完全相同。 -- 五条 FIXME 标记全部就位,每条都写明其意图中的新名字。 -- 新增与更新的组 README 构成完整的双语三文件配对;packages/README.md 的层级结构表与目录树一致;docs/module-graph.md 已重新生成。 -- `pnpm run typecheck`、`pnpm run test`、`pnpm run doc-sync`、`pnpm run hygiene` 全部通过,且单元/快照套件在移动前后收集到相同的测试文件(测试配置里按组划定的 glob 不得无声地匹配为空)。 - -## Risks - -- **开放 PR 的冲突。** 每个触碰被移动文件的开放 PR 都必须跨过这次移动做变基。缓解方式:作为单个 PR 在低活动窗口期落地、在 PR 正文公布映射表,且移动都是纯改名(重命名检测可机械化解决大多数改动块)。 -- **引用漂移。** 引用旧路径的文档或脚本可能变得陈旧;`verify-package-paths` 正是为包移动造成的漂移而设,并随 `doc-sync`(文档同步门禁)运行。 -- **翻译负担。** 五组新的组 README 三文件配对,加上既有配对的修改,都随实现 PR 一同落地。 -- **推迟的改名按设计就是发布阻塞项。** 日后若某条 FIXME 被证明不对,必须连同理由显式移除,绝不允许无声消失。 -- **单包组依然存在**(`boot/`、`self-modification/`,以及 `acp/` 等既有单包组)。这是有意接受的:每个都是角色完备的整体而非某个家族的碎片,一个名实相符的小组胜过一次徒有其名的合并。 +- 目录树与映射表一致:重组的六个组恰好持有所列成员;`ui/`、`sdk/`、`telemetry/`、`timeout/`、`cordis/`、`session-persistence/`、`session-projection/`、`session-title/` 这些组不复存在;其余每个组的内容不变。workspace 的包名集合在前后完全相同(npm 改名为零),五条 FIXME 标记钉住推迟的改名。日后若某条 FIXME 被证明不对,必须连同理由显式移除,绝不允许无声消失。 +- 结果由以下检查钉住:`pnpm run typecheck`、每个被移动组的单元测试套件、`verify-package-paths`、`verify-md-links` 与全语料翻译配对在移动后的树上全部通过;`vitest.snapshot.config.ts` 中按组划定的测试 glob 随移动一并改写,套件收集到与移动前相同的测试文件(glob 匹配为空会无声地丢失覆盖)。 +- 每个触碰被移动文件的开放 PR 都跨过这次移动做一次变基;PR 正文中的映射表与重命名检测可机械化解决大多数改动块。 +- 单包组依然存在(`boot/`、`self-modification/`,以及 `acp/` 等既有单包组)。这是有意接受的:每个都是角色完备的整体而非某个家族的碎片,一个名实相符的小组胜过一次徒有其名的合并。 +- 在推迟的改名落地之前,`scaffold/` 的目录名与其 npm 名并不一致——这是唯一的过渡性不对称,由 `tsconfig.base.json` 里三条显式 `paths` 映射承载,并由 FIXME 改名最终消除。 - **这次变更放弃了什么:** 功能上一无所失——变更只关乎导航。肌肉记忆和指向旧 GitHub 路径的外部链接会失效;在 pre-release、尚无外部消费者的前提下,这可以接受。 diff --git a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.i18n.yaml index ba4444166c..e083adcca5 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md -2026-08-04-credentials-yaml-and-user-environment-layer.md: 4ecbc41adf4e22c74ecf425c2caf628efdf7cf54 -2026-08-04-credentials-yaml-and-user-environment-layer.zh.md: 370179b442783f4f8ecd8e3badbd236a924f5f81 +2026-08-04-credentials-yaml-and-user-environment-layer.md: 44bdea243671093b34a6338ed1c95bf25bcc78f4 +2026-08-04-credentials-yaml-and-user-environment-layer.zh.md: 5f724c7a12365e6e87e03b5926f837a5251b6118 diff --git a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md index 4ecbc41adf..44bdea2436 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md +++ b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.md @@ -23,7 +23,7 @@ OPENAI_API_KEY: sk-… Because the document holds credentials and nothing else, every deviation is a rejection rather than a skipped entry: a non-mapping root, a key that is not a POSIX identifier, a non-string value, an empty string, a duplicate key, and malformed YAML all fail — loud at boot and at a write, warn-and-keep-the-last-good-snapshot on a live reload. A silently ignored key would read as "the secret I stored has no effect", which is the failure this change exists to remove. The dotenv physical-line editor is replaced by a patch of the parsed document, so comments and untouched entries keep their formatting, any string value round-trips (multi-line included), and no entry is unwritable for want of a quoting style. The writer lock, read-modify-write, atomic `0600` write under a `0700` directory, exact-path watcher, content-equality self-write suppression, and quiescent disposal are unchanged. -**`$DSH_HOME/.env` is the user's ordinary environment layer.** `loadLayeredEnv` in [`dsh-app-boot`](../../../../packages/ui/app-boot/README.md) parses the invoking directory's `.env` and then the Harness home's, giving `user < project < inherited` by materializing each accepted value only when the process has no higher-layer value. The Harness home is resolved from the inherited environment *before* either file loads, so a project `.env` cannot redirect which user document is read. Only the product CLI layers these files; SDK and example bins keep loading their own directory through `loadEnv` and must not inherit a developer's `$DSH_HOME`. +**`$DSH_HOME/.env` is the user's ordinary environment layer.** `loadLayeredEnv` in [`dsh-app-boot`](../../../../packages/boot/app-boot/README.md) parses the invoking directory's `.env` and then the Harness home's, giving `user < project < inherited` by materializing each accepted value only when the process has no higher-layer value. The Harness home is resolved from the inherited environment *before* either file loads, so a project `.env` cannot redirect which user document is read. Only the product CLI layers these files; SDK and example bins keep loading their own directory through `loadEnv` and must not inherit a developer's `$DSH_HOME`. Credential precedence distinguishes the inherited environment from discovered files: the inherited value stays the read-only per-run override, the managed document wins next, and project then user `.env` values remain writable fallbacks. A `set` therefore replaces a discovered-file value instead of rejecting a write that only the flattened `process.env` view would consider shadowed. @@ -38,7 +38,7 @@ There is no migration. A key already in `$DSH_HOME/.env` keeps resolving as a fa ## Alternatives considered -**Keep one `$DSH_HOME/.env` and teach the CLI to hoist it.** Rejected: hoisting the store is precisely what makes stored keys unrotatable, which is why [app-boot documented the exclusion](../../../../packages/ui/app-boot/README.md) in the first place. The conflict is the file's two jobs, not the loader. +**Keep one `$DSH_HOME/.env` and teach the CLI to hoist it.** Rejected: hoisting the store is precisely what makes stored keys unrotatable, which is why [app-boot documented the exclusion](../../../../packages/boot/app-boot/README.md) in the first place. The conflict is the file's two jobs, not the loader. **`$DSH_HOME/.credentials.env` — a second dotenv file.** Rejected: dotenv suits an environment layer but cannot express "a managed document indexed by credential reference". It cannot reject a non-string or an unaddressable key, and its line editor already refused values it could not quote, leaving entries readable but unwritable. diff --git a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.zh.md b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.zh.md index 370179b442..5f724c7a12 100644 --- a/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-04-credentials-yaml-and-user-environment-layer.zh.md @@ -23,7 +23,7 @@ OPENAI_API_KEY: sk-… 因为该文档只存放凭据、别无他物,任何偏离都是拒绝而不是跳过条目:非 mapping 的根、非 POSIX 标识符的键、非字符串值、空字符串、重复键以及格式错误的 YAML 全部失败——启动时和写入时响亮失败,运行期热重载则告警并保留最后可用快照。被静默忽略的键读起来就是「我存进去的密钥没有生效」,而这正是本次变更要消除的失败。dotenv 物理行编辑器被替换为对已解析文档打补丁,因此注释与未触及条目的排版都会保留,任何字符串值都能往返(含多行),也不会再有条目因为缺少可用引号样式而不可写。写锁、read-modify-write、`0700` 目录下的 `0600` 原子写、精确路径 watcher、按内容相等抑制自写、以及 dispose 时的完全停稳,均保持不变。 -**`$DSH_HOME/.env` 是用户的普通环境层。** [`dsh-app-boot`](../../../../packages/ui/app-boot/README.md) 中的 `loadLayeredEnv` 先解析调用目录的 `.env`,再解析 Harness home 的,并且只在进程中没有更高层值时物化每个已接受的值,从而得到 `用户 < 项目 < 继承`。Harness home 在两个文件加载*之前*就从继承的环境解析完毕,因此项目 `.env` 无法改变读取哪份用户文档。只有产品 CLI(命令行界面)叠加这两个文件;SDK 与示例 bin 仍通过 `loadEnv` 加载各自的目录,绝不继承开发者的 `$DSH_HOME`。 +**`$DSH_HOME/.env` 是用户的普通环境层。** [`dsh-app-boot`](../../../../packages/boot/app-boot/README.md) 中的 `loadLayeredEnv` 先解析调用目录的 `.env`,再解析 Harness home 的,并且只在进程中没有更高层值时物化每个已接受的值,从而得到 `用户 < 项目 < 继承`。Harness home 在两个文件加载*之前*就从继承的环境解析完毕,因此项目 `.env` 无法改变读取哪份用户文档。只有产品 CLI(命令行界面)叠加这两个文件;SDK 与示例 bin 仍通过 `loadEnv` 加载各自的目录,绝不继承开发者的 `$DSH_HOME`。 凭据优先级会区分继承环境与发现的文件:继承值仍是只读的按次覆盖,其后是受管文档,再后是仍可写的项目与用户 `.env` 后备值。因此 `set` 会替换发现文件中的值,而不是因为扁平化的 `process.env` 视图认为写入会被遮蔽就加以拒绝。 @@ -38,7 +38,7 @@ OPENAI_API_KEY: sk-… ## Alternatives considered -**保留单一的 `$DSH_HOME/.env`,让 CLI 去提升它。** 否决:提升存储本身正是让已存密钥无法轮换的原因,这也是 [app-boot 当初记录该排除](../../../../packages/ui/app-boot/README.md)的理由。冲突来自这个文件的两份工作,而不是加载器。 +**保留单一的 `$DSH_HOME/.env`,让 CLI 去提升它。** 否决:提升存储本身正是让已存密钥无法轮换的原因,这也是 [app-boot 当初记录该排除](../../../../packages/boot/app-boot/README.md)的理由。冲突来自这个文件的两份工作,而不是加载器。 **`$DSH_HOME/.credentials.env`——第二个 dotenv 文件。** 否决:dotenv 适合环境层,却无法表达「一份按凭据引用索引的受管文档」。它无法拒绝非字符串或无法寻址的键,而且它的行编辑器本来就会拒绝无法加引号的值,留下可读却不可写的条目。 diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml index 4931fa907b..fda3830670 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md -2026-07-31-fail-loud-releases-the-terminal.md: 2a6e7fcbbdd5d35bcf70dee09fdb9e5592486b78 -2026-07-31-fail-loud-releases-the-terminal.zh.md: f75c21cf79b241e6714c10ec7df9ac25f3d978b4 +2026-07-31-fail-loud-releases-the-terminal.md: 5fe812e5fe64a1fab8ba9808d87f3581b6f5b206 +2026-07-31-fail-loud-releases-the-terminal.zh.md: 64459614dc3376a1c773de72cd36aa455894a03c diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md index 2a6e7fcbbd..5fe812e5fe 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md +++ b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.md @@ -52,7 +52,7 @@ The guarantee belongs to whichever bin owns the terminal: a surface that grabs t ## Testing -`packages/ui/app-boot/tests/app-boot.spec.ts` covers the release contract: the hook is awaited before the exit commits, a rejecting hook still exits 1, a never-settling hook exits after `FAIL_LOUD_RELEASE_TIMEOUT_MS`, and a burst of rejections reports only the first while the release still completes. +`packages/boot/app-boot/tests/app-boot.spec.ts` covers the release contract: the hook is awaited before the exit commits, a rejecting hook still exits 1, a never-settling hook exits after `FAIL_LOUD_RELEASE_TIMEOUT_MS`, and a burst of rejections reports only the first while the release still completes. Those fake-process tests cannot observe the two failure modes that matter most — process exit code with a real event loop, and terminal state after exit — so the regression lives in `apps/cli/tests/tui-keyless-smoke.e2e.ts`. It boots the shipped tree in a real PTY over `fixtures/tui-invalid-provider.cordis.yml` (a list-shaped `providers`, the mistake users actually make), expects exit 1, and asserts the captured bytes contain both the labelled boot rejection (`dsh: plugin tree failed to load:`) and `ESC[?2004l`. The same case pins the boot path end to end: it caught the [HMR initial-scan boot deadlock](2026-08-03-hmr-initial-scan-boot-deadlock.md) that silently exited 13 with the terminal stranded. diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md index f75c21cf79..64459614dc 100644 --- a/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-07-31-fail-loud-releases-the-terminal.zh.md @@ -52,7 +52,7 @@ Loader 并发挂载各个条目,因此条目失败的顺序并不等于启动 ## Testing -`packages/ui/app-boot/tests/app-boot.spec.ts` 覆盖 release 契约:退出提交前会等待该回调;回调 rejection 时仍退出 1;永不结算的回调会在 `FAIL_LOUD_RELEASE_TIMEOUT_MS` 后退出;以及一连串 rejection 只报告第一个,同时 release 仍能跑完。 +`packages/boot/app-boot/tests/app-boot.spec.ts` 覆盖 release 契约:退出提交前会等待该回调;回调 rejection 时仍退出 1;永不结算的回调会在 `FAIL_LOUD_RELEASE_TIMEOUT_MS` 后退出;以及一连串 rejection 只报告第一个,同时 release 仍能跑完。 这些基于假进程的测试无法观测到最关键的两种失败形态——真实事件循环下的进程退出码,以及退出之后的终端状态——因此回归用例放在 `apps/cli/tests/tui-keyless-smoke.e2e.ts`。它在真实 PTY 中以 `fixtures/tui-invalid-provider.cordis.yml`(`providers` 为列表形状,正是用户真实会犯的错误)启动出厂配置树,期望退出码为 1,并断言捕获到的字节流同时包含带标签的启动 rejection(`dsh: plugin tree failed to load:`)与 `ESC[?2004l`。同一用例端到端钉住了启动路径:正是它发现了以 13 静默退出、终端状态被残留的 [HMR 初始扫描启动死锁](2026-08-03-hmr-initial-scan-boot-deadlock.md)。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml index 4752010ef6..36c7895ac2 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md -2026-08-03-cli-signal-shutdown-escalation.md: 7c9715c37ee57be9fa0f67af0c19f0bfa84845da -2026-08-03-cli-signal-shutdown-escalation.zh.md: f3485edc9e453c0b774f442bfce6d678d63f2224 +2026-08-03-cli-signal-shutdown-escalation.md: c8aac6e2be927bd1f4a445c00e0aaa870b10a465 +2026-08-03-cli-signal-shutdown-escalation.zh.md: 66fe7062ea35a6cc91dbdd6644088cb086a15439 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md index 7c9715c37e..c8aac6e2be 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md +++ b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.md @@ -49,4 +49,4 @@ The controller is launcher infrastructure rather than a Cordis plugin: it makes `apps/cli/tests/headless-shutdown.e2e.ts` boots the real shipped Web/headless Loader tree in a PTY with a test-only plugin whose disposer announces entry and never settles. The test sends SIGINT after the observation URL, waits for proof that disposal started, sends SIGINT again, and requires exit 130. The source/artifact launch resolver keeps the same regression on both execution planes. This PTY case covers the user-visible process state; no model-output snapshot changes. -`packages/telemetry/session-telemetry-otel/tests/otel.spec.ts` holds a real OTLP request open after timer export begins and pins that Cordis disposal returns at `shutdownTimeoutMillis`, despite the SDK's `forceFlush()` remaining pending. The collector is then released so the still-observed provider Promise settles cleanly. +`packages/session/session-telemetry-otel/tests/otel.spec.ts` holds a real OTLP request open after timer export begins and pins that Cordis disposal returns at `shutdownTimeoutMillis`, despite the SDK's `forceFlush()` remaining pending. The collector is then released so the still-observed provider Promise settles cleanly. diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md index f3485edc9e..66fe7062ea 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-03-cli-signal-shutdown-escalation.zh.md @@ -49,4 +49,4 @@ headless 对完成的轮次仍以 0 退出,对其他轮次结束原因或 API `apps/cli/tests/headless-shutdown.e2e.ts` 在 PTY 中启动真实交付的 Web/headless Loader 插件树,并挂载一个仅用于测试的插件;该插件的 disposer 会声明已经进入清理流程,但永不结算。测试在观察地址出现后发送 SIGINT,等待 dispose 已启动的证据,再次发送 SIGINT,并要求进程以 130 退出。源码/产物启动解析器使两个执行平面都覆盖同一项回归。该 PTY 用例覆盖用户可见的进程状态;模型输出快照没有变化。 -`packages/telemetry/session-telemetry-otel/tests/otel.spec.ts` 在定时器导出开始后保持一条真实 OTLP 请求打开,并固定以下行为:即使 SDK 的 `forceFlush()` 仍待结算,Cordis dispose 也会在 `shutdownTimeoutMillis` 到期时返回。随后测试释放 collector,使仍受观察的提供方 Promise 干净结算。 +`packages/session/session-telemetry-otel/tests/otel.spec.ts` 在定时器导出开始后保持一条真实 OTLP 请求打开,并固定以下行为:即使 SDK 的 `forceFlush()` 仍待结算,Cordis dispose 也会在 `shutdownTimeoutMillis` 到期时返回。随后测试释放 collector,使仍受观察的提供方 Promise 干净结算。 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.i18n.yaml index 170627ea76..51fbed7ba9 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md -2026-08-03-hmr-initial-scan-boot-deadlock.md: 4b3e259c216d258c321ab06c41225b33ed240d19 -2026-08-03-hmr-initial-scan-boot-deadlock.zh.md: ce1bc8396ac6e7fb6ecb1647fe2b29cdc788c7e1 +2026-08-03-hmr-initial-scan-boot-deadlock.md: 1a70fbafc74a1bf7024c675ddad82acefc995b72 +2026-08-03-hmr-initial-scan-boot-deadlock.zh.md: 5f8d50c4c0103eb2209eafb89cf34233c4c1bc35 diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md index 4b3e259c21..1a70fbafc7 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md +++ b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.md @@ -38,4 +38,4 @@ One latent gap remains: a config edit made during a *failing* initial apply can ## Testing -The `dsh` invalid-provider PTY case in `apps/cli/tests/tui-keyless-smoke.e2e.ts` pins the end-to-end contract: exit 1, the labelled `dsh: plugin tree failed to load:` diagnostic naming `$.providers`, and the bracketed-paste reset proving the tree was disposed. Before this fix the same case observed exit 13 with no diagnostic. Reload behavior stays covered by `packages/ui/app-boot/tests/config-reload.spec.ts` and `packages/ui/app-boot/tests/hmr-config.spec.ts`. +The `dsh` invalid-provider PTY case in `apps/cli/tests/tui-keyless-smoke.e2e.ts` pins the end-to-end contract: exit 1, the labelled `dsh: plugin tree failed to load:` diagnostic naming `$.providers`, and the bracketed-paste reset proving the tree was disposed. Before this fix the same case observed exit 13 with no diagnostic. Reload behavior stays covered by `packages/boot/app-boot/tests/config-reload.spec.ts` and `packages/boot/app-boot/tests/hmr-config.spec.ts`. diff --git a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.zh.md b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.zh.md index ce1bc8396a..5f8d50c4c0 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-03-hmr-initial-scan-boot-deadlock.zh.md @@ -38,4 +38,4 @@ ## 测试 -`apps/cli/tests/tui-keyless-smoke.e2e.ts` 中 `dsh` 无效 provider 的 PTY 用例钉住了端到端契约:以 1 退出、带标签的 `dsh: plugin tree failed to load:` 诊断指明 `$.providers`、以及证明整棵树已被释放的 bracketed-paste 复位序列。此修复之前,同一用例观察到的是无诊断的 exit 13。重载行为仍由 `packages/ui/app-boot/tests/config-reload.spec.ts` 与 `packages/ui/app-boot/tests/hmr-config.spec.ts` 覆盖。 +`apps/cli/tests/tui-keyless-smoke.e2e.ts` 中 `dsh` 无效 provider 的 PTY 用例钉住了端到端契约:以 1 退出、带标签的 `dsh: plugin tree failed to load:` 诊断指明 `$.providers`、以及证明整棵树已被释放的 bracketed-paste 复位序列。此修复之前,同一用例观察到的是无诊断的 exit 13。重载行为仍由 `packages/boot/app-boot/tests/config-reload.spec.ts` 与 `packages/boot/app-boot/tests/hmr-config.spec.ts` 覆盖。 diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml b/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml index 8374a59b63..9f60321cc8 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-28-feedback-command.md -2026-07-28-feedback-command.md: 56660b3796c44d3d510dd2901e6a155498673d88 -2026-07-28-feedback-command.zh.md: ec03a5c4a0e96d5350085c3d8d26f2523962f1b5 +2026-07-28-feedback-command.md: 78cc9e89a5811b3f1520bae2bd971cbcf2522ede +2026-07-28-feedback-command.zh.md: c8870ca2988501d7899ae826e1bbcbad0bef5678 diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.md b/.agents/notes/implemented/feature/2026-07-28-feedback-command.md index 56660b3796..78cc9e89a5 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.md +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.md @@ -46,7 +46,7 @@ Surrounding whitespace is discarded, but nothing else is parsed. `/feedback /pla **Make `/feedback` a true no-op that records nothing.** The most literal reading of "does not do anything". Rejected because it makes the command pointless: the stated requirement was that the remark reach the session log. -**Register the command inside an existing package** such as `packages/ui/commands`. Avoids a new group and its README pair. Rejected: `ctx.commands` is the registry, not a home for arbitrary command implementations, and the requester asked for a standalone package. +**Register the command inside an existing package** such as `packages/interaction/commands`. Avoids a new group and its README pair. Rejected: `ctx.commands` is the registry, not a home for arbitrary command implementations, and the requester asked for a standalone package. **Parse structure out of the text** (category prefixes, severity markers). Rejected as speculative: no consumer needs that structure, and any control-word grammar makes the corresponding literal feedback unrecordable. Verbatim text is the widest surface a future consumer can narrow; a parsed one cannot be widened after the fact. diff --git a/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md b/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md index ec03a5c4a0..c8870ca298 100644 --- a/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md +++ b/.agents/notes/implemented/feature/2026-07-28-feedback-command.zh.md @@ -46,7 +46,7 @@ Status: implemented **让 `/feedback` 成为真正的空操作,什么都不记录。** 这是对「什么都不做」最字面的理解。已否决:这会使命令失去意义——明确的要求是让这条评论进入会话日志。 -**在现有包中注册该命令**,例如 `packages/ui/commands`。可省去新分组及其双语 README。已否决:`ctx.commands` 是注册表,而不是任意命令实现的归属地;且请求者明确要求独立的包。 +**在现有包中注册该命令**,例如 `packages/interaction/commands`。可省去新分组及其双语 README。已否决:`ctx.commands` 是注册表,而不是任意命令实现的归属地;且请求者明确要求独立的包。 **从文本中解析结构**(类别前缀、严重程度标记)。已否决,属于投机设计:没有消费方需要该结构,而任何控制词语法都会让对应的字面反馈无法记录。原样文本是未来消费方可以收窄的最宽接口;而已被解析的接口无法事后放宽。 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml index 620fa2291f..8109eb4854 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md 2026-07-24-domain-kv-storage-and-workspace.md: 1cb2bc4b29c196dbb33292547c75d01a37f06772 -2026-07-24-domain-kv-storage-and-workspace.zh.md: 15460e305d2d9aca3009025cf0218eb6c54c7237 +2026-07-24-domain-kv-storage-and-workspace.zh.md: 2904065a7374803495a9bbf8ff4e044310d9bd02 diff --git a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md index 15460e305d..2904065a73 100644 --- a/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md +++ b/.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md @@ -6,7 +6,7 @@ Status: proposed ## 问题 -host 侧唯一的持久化面是 session 事件日志(`packages/session-persistence`:仅追加、一 session 一文件)。凡是"不属于某个 session"的信息就没有落盘处,眼下有两个真实需求: +host 侧唯一的持久化面是 session 事件日志(`packages/session/session-persistence`:仅追加、一 session 一文件)。凡是"不属于某个 session"的信息就没有落盘处,眼下有两个真实需求: - **workspace 实体**。GUI 要把 workspace 做成真实对象:路径、标题、关联 session 清单。归属关系由 workspace 持有——"哪些 session 属于这个 workspace"不是任何单个 session 自己的事实,塞进 session log 语义不成立。此前 workspace 只是 sidebar 上按 cwd 分组的视觉概念,没有实体(该结论已被推翻)。 - **session 动态元信息**(可预见的第二个消费方)。冷会话列表只读日志首行 header(创建时的不可变快照),title、结束状态这类随会话推进变化的信息拿不到;补齐方向是 sidecar 元数据表——正是一张按 key 高频点更新的 KV 表。 diff --git a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml b/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml deleted file mode 100644 index cdd772386f..0000000000 --- a/.agents/notes/proposed/architecture/2026-07-29-package-regrouping.i18n.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each -# side as of the last confirmed-consistent state. Both languages carry equal authority; -# after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-29-package-regrouping.md -2026-07-29-package-regrouping.md: 9a56f9f5f1e0aef7d1f9efe60168ec1b0f41cce8 -2026-07-29-package-regrouping.zh.md: c49c5a1daffaff599f70f36b62e4dd3b5de3e5cf diff --git a/AGENTS.md b/AGENTS.md index 35fbc54ca6..dcb001768e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,22 +25,22 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// web/ web seam + search/fetch providers + model-facing web tools compact/ compaction seam + basic backend context/ request-context plugins - subagent/ subagent seam + spawn/fork/ACP backends + delegation tool - bundle/ profile plugin bundles: installable patch layers for dsh --profile + subagent/ subagent seam + spawn/fork/ACP backends + tool + bundle/ installable dsh --profile patch-layer bundles workflow/ workflow seam + worker-thread engine + workflow tool todo/ todo_write tool - plan/ plan mode as logged collaboration state - guard/ loop-hygiene plugins + tool-call timeouts - self-modification/ the agent inspects/mounts plugins in its runtime - hooks/ Claude Code/Codex hook bridges + shared wire-protocol library - session/ durable session data plane: persistence, projection, titles, reporting - settings/ user-settings seam + file-backed provider - credentials/ credential-reference seam + env-over-.env provider + plan/ plan mode as logged state + guard/ loop-hygiene + tool-timeout plugins + self-modification/ the agent inspects/mounts its own plugins + hooks/ Claude Code/Codex hook bridges + wire-protocol library + session/ durable session data: persistence, projection, titles, telemetry + settings/ user-settings seam + file provider + credentials/ credential-reference seam + env/.env provider acp/ automation-only Agent Client Protocol server - interaction/ approval/interaction seams, permission, commands, ask-user tool - boot/ shared app-bin boot glue - scaffold/ project tooling: helper, launcher, initializer, SDK wire protocol + ends - examples/ demo bundles (agent-spine + CLI/ACP/JSON-RPC bins) leaves load + interaction/ approval/interaction seams, permission, commands, ask-user + boot/ shared app-bin glue + scaffold/ project tooling: helper, launcher, initializer, SDK protocol + examples/ demo bundles (agent-spine + CLI/ACP/JSON-RPC bins) support/ dev/test infrastructure util/ zero-dependency utilities python/ Python SDK and bundled runtime (see python/README.md) diff --git a/apps/cli/reference/README.i18n.yaml b/apps/cli/reference/README.i18n.yaml index 27e391320a..325de8db4e 100644 --- a/apps/cli/reference/README.i18n.yaml +++ b/apps/cli/reference/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write apps/cli/reference/README.md -README.md: 496cecdb64e3254a2a77690f55f760b4cd90b521 -README.zh.md: 4673bf764347307a9b91e2a5474a8439cf67b481 +README.md: e616b420d60e0fa372bf38950207245cb60ced98 +README.zh.md: a65140e6dc9374e21bdd3018bce45432794a7b10 diff --git a/apps/cli/reference/README.md b/apps/cli/reference/README.md index 496cecdb64..e616b420d6 100644 --- a/apps/cli/reference/README.md +++ b/apps/cli/reference/README.md @@ -69,7 +69,7 @@ The base bundle mounts the native DeepSeek adapter, settings and credential prov Session events stream as OTLP/HTTP logs by default. `DSH_TELEMETRY_OTLP_URL` selects another collector. Any non-empty `DSH_TELEMETRY_DISABLED` disables the telemetry row before boot. The shipped base has no telemetry redaction rule, so exported records can contain message text, tool arguments and results, and workspace paths; the [telemetry Agent Note](../../../.agents/notes/implemented/feature/2026-07-31-web-telemetry-default-mount.md) owns that deployment decision. -The empty `repository-plugins` row lets profile patch layers mount prepared immutable repository Plugin generations. See the [repository Plugin contract](../../../packages/cordis/repository-plugin/README.md#standalone-app-configuration). The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox. +The empty `repository-plugins` row lets profile patch layers mount prepared immutable repository Plugin generations. See the [repository Plugin contract](../../../packages/self-modification/repository-plugin/README.md#standalone-app-configuration). The CLI also ships `@deepseek-ai/dsh-mcp-client` as a dependency for patch layers, but no MCP server is enabled by default because each server command is trusted executable code outside the agent sandbox. ## Source launcher diff --git a/apps/cli/reference/README.zh.md b/apps/cli/reference/README.zh.md index 4673bf7643..a65140e6dc 100644 --- a/apps/cli/reference/README.zh.md +++ b/apps/cli/reference/README.zh.md @@ -69,7 +69,7 @@ dsh web --dump-config 会话事件默认作为 OTLP/HTTP 日志流式发送。`DSH_TELEMETRY_OTLP_URL` 选择其他 collector。任何非空 `DSH_TELEMETRY_DISABLED` 都会在启动前禁用遥测配置行。随附基础配置没有遥测脱敏规则,因此导出的记录可能包含消息文本、工具参数与结果以及 workspace 路径;该部署决策由[遥测 Agent Note](../../../.agents/notes/implemented/feature/2026-07-31-web-telemetry-default-mount.md)负责。 -空 `repository-plugins` 行让 profile 的 patch 层能够挂载已准备的不可变 repository Plugin generation。参见 [repository Plugin 契约](../../../packages/cordis/repository-plugin/README.md#standalone-app-configuration)。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。 +空 `repository-plugins` 行让 profile 的 patch 层能够挂载已准备的不可变 repository Plugin generation。参见 [repository Plugin 契约](../../../packages/self-modification/repository-plugin/README.md#standalone-app-configuration)。CLI 还随附 `@deepseek-ai/dsh-mcp-client` 作为供 patch 层使用的依赖,但默认不启用 MCP 服务器,因为每条服务器命令都是 agent(智能体)沙箱之外的受信任可执行代码。 ## 源码启动器 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index c74358fd39..254d85ccd1 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -76,7 +76,7 @@ export interface Config { } ``` -Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session-persistence/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) +Depends on: [`agentCore`](../packages/examples/agent-spine-demo/src/index.ts) · [`JsonlCompression`](../packages/session/session-persistence-jsonl/src/index.ts) · [`ToolsConfig`](#deepseek-aidsh-tools) Source: [`packages/examples/acp-demo/src/index.ts:39`](../packages/examples/acp-demo/src/index.ts) @@ -639,7 +639,7 @@ export interface JsonRpcConfig { Depends on: `Readable` (`node:stream`) · `Writable` (`node:stream`) -Source: [`packages/ui/jsonrpc/src/index.ts:25`](../packages/ui/jsonrpc/src/index.ts) +Source: [`packages/scaffold/server/src/index.ts:29`](../packages/scaffold/server/src/index.ts) ## `@deepseek-ai/dsh-llm-deepseek` @@ -1046,7 +1046,7 @@ export interface PresetSpec { Depends on: [`ApprovalPolicy`](core-data-structures/approval.md) · [`SandboxMode`](core-data-structures/sandbox.md) -Source: [`packages/ui/permission/src/index.ts:140`](../packages/ui/permission/src/index.ts) +Source: [`packages/interaction/permission/src/index.ts:140`](../packages/interaction/permission/src/index.ts) ## `@deepseek-ai/dsh-plan-mode` @@ -1182,7 +1182,7 @@ export interface Config { } ``` -Source: [`packages/cordis/repository-plugin/src/index.ts:42`](../packages/cordis/repository-plugin/src/index.ts) +Source: [`packages/self-modification/repository-plugin/src/index.ts:42`](../packages/self-modification/repository-plugin/src/index.ts) ## `@deepseek-ai/dsh-sandbox-local` @@ -1273,7 +1273,7 @@ export interface Config { export type JsonlCompression = 'zstd' | 'none' ``` -Source: [`packages/session-persistence/session-persistence-jsonl/src/index.ts:59`](../packages/session-persistence/session-persistence-jsonl/src/index.ts) +Source: [`packages/session/session-persistence-jsonl/src/index.ts:59`](../packages/session/session-persistence-jsonl/src/index.ts) ## `@deepseek-ai/dsh-session-persistence-sqlite` @@ -1316,7 +1316,7 @@ export interface Config { export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist' ``` -Source: [`packages/session-persistence/session-persistence-sqlite/src/index.ts:67`](../packages/session-persistence/session-persistence-sqlite/src/index.ts) +Source: [`packages/session/session-persistence-sqlite/src/index.ts:67`](../packages/session/session-persistence-sqlite/src/index.ts) ## `@deepseek-ai/dsh-session-projection-cache` @@ -1337,7 +1337,7 @@ export interface Config { } ``` -Source: [`packages/session-projection/session-projection-cache/src/index.ts:42`](../packages/session-projection/session-projection-cache/src/index.ts) +Source: [`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts) ## `@deepseek-ai/dsh-session-query-sqlite` @@ -1437,7 +1437,7 @@ export enum TelemetryMode { Depends on: `BatchLogRecordProcessorOptions` (`@opentelemetry/sdk-logs`) · `OTLPExporterNodeConfigBase` (`@opentelemetry/otlp-exporter-base`) -Source: [`packages/telemetry/session-telemetry-otel/src/index.ts:80`](../packages/telemetry/session-telemetry-otel/src/index.ts) +Source: [`packages/session/session-telemetry-otel/src/index.ts:80`](../packages/session/session-telemetry-otel/src/index.ts) ## `@deepseek-ai/dsh-session-title` @@ -1455,7 +1455,7 @@ export interface Config { } ``` -Source: [`packages/session-title/session-title/src/index.ts:79`](../packages/session-title/session-title/src/index.ts) +Source: [`packages/session/session-title/src/index.ts:79`](../packages/session/session-title/src/index.ts) ## `@deepseek-ai/dsh-session-title-all-messages-llm` @@ -1466,9 +1466,9 @@ Requires: `sessionTitle` · `llm` · `sessions` export type Config = SessionTitleLlmConfig ``` -Depends on: [`SessionTitleLlmConfig`](../packages/session-title/session-title-llm/src/index.ts) +Depends on: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/index.ts) -Source: [`packages/session-title/session-title-all-messages-llm/src/index.ts:15`](../packages/session-title/session-title-all-messages-llm/src/index.ts) +Source: [`packages/session/session-title-all-messages-llm/src/index.ts:15`](../packages/session/session-title-all-messages-llm/src/index.ts) ## `@deepseek-ai/dsh-session-title-first-message-llm` @@ -1479,9 +1479,9 @@ Requires: `sessionTitle` · `llm` · `sessions` export type Config = SessionTitleLlmConfig ``` -Depends on: [`SessionTitleLlmConfig`](../packages/session-title/session-title-llm/src/index.ts) +Depends on: [`SessionTitleLlmConfig`](../packages/session/session-title-llm/src/index.ts) -Source: [`packages/session-title/session-title-first-message-llm/src/index.ts:15`](../packages/session-title/session-title-first-message-llm/src/index.ts) +Source: [`packages/session/session-title-first-message-llm/src/index.ts:15`](../packages/session/session-title-first-message-llm/src/index.ts) ## `@deepseek-ai/dsh-settings-local` @@ -1955,7 +1955,7 @@ export interface Config { } ``` -Source: [`packages/cordis/tool-cordis/src/index.ts:25`](../packages/cordis/tool-cordis/src/index.ts) +Source: [`packages/self-modification/tool-cordis/src/index.ts:25`](../packages/self-modification/tool-cordis/src/index.ts) ## `@deepseek-ai/dsh-tool-fs` @@ -2365,7 +2365,7 @@ export interface Config { export type ApprovalPolicy = 'ask' | 'never' ``` -Source: [`packages/ui/user-approval/src/index.ts:178`](../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:178`](../packages/interaction/user-approval/src/index.ts) ## `@deepseek-ai/dsh-web` @@ -2602,7 +2602,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-command-compact` — requires `commands` · `compact` ([`packages/compact/command-compact/src/index.ts`](../packages/compact/command-compact/src/index.ts)) - `@deepseek-ai/dsh-command-feedback` — requires `commands` ([`packages/feedback/command-feedback/src/index.ts`](../packages/feedback/command-feedback/src/index.ts)) - `@deepseek-ai/dsh-command-goal` — requires `commands` · `goals` ([`packages/goal/command-goal/src/index.ts`](../packages/goal/command-goal/src/index.ts)) -- `@deepseek-ai/dsh-commands` ([`packages/ui/commands/src/index.ts`](../packages/ui/commands/src/index.ts)) +- `@deepseek-ai/dsh-commands` ([`packages/interaction/commands/src/index.ts`](../packages/interaction/commands/src/index.ts)) - `@deepseek-ai/dsh-fs-e2b` — requires `e2b` ([`packages/e2b/fs-e2b/src/index.ts`](../packages/e2b/fs-e2b/src/index.ts)) - `@deepseek-ai/dsh-fs-policy` ([`packages/fs/fs-policy/src/index.ts`](../packages/fs/fs-policy/src/index.ts)) - `@deepseek-ai/dsh-goal-session` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-session/src/index.ts`](../packages/goal/goal-session/src/index.ts)) @@ -2612,17 +2612,17 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-lsp` ([`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts)) - `@deepseek-ai/dsh-pty` ([`packages/pty/pty/src/index.ts`](../packages/pty/pty/src/index.ts)) - `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts)) -- `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session-persistence/session-checkpoint-policy/src/index.ts`](../packages/session-persistence/session-checkpoint-policy/src/index.ts)) -- `@deepseek-ai/dsh-session-projection` ([`packages/session-projection/session-projection/src/index.ts`](../packages/session-projection/session-projection/src/index.ts)) +- `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session/session-checkpoint-policy/src/index.ts`](../packages/session/session-checkpoint-policy/src/index.ts)) +- `@deepseek-ai/dsh-session-projection` ([`packages/session/session-projection/src/index.ts`](../packages/session/session-projection/src/index.ts)) - `@deepseek-ai/dsh-skill-badge` — requires `skills` ([`packages/skill/skill-badge/src/index.ts`](../packages/skill/skill-badge/src/index.ts)) - `@deepseek-ai/dsh-storage` ([`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts)) - `@deepseek-ai/dsh-subagent` ([`packages/subagent/subagent/src/index.ts`](../packages/subagent/subagent/src/index.ts)) - `@deepseek-ai/dsh-subprocess-local` ([`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts)) - `@deepseek-ai/dsh-tasks-local` ([`packages/tasks/tasks-local/src/index.ts`](../packages/tasks/tasks-local/src/index.ts)) -- `@deepseek-ai/dsh-timeout-policy` — requires `tools` ([`packages/timeout/timeout-policy/src/index.ts`](../packages/timeout/timeout-policy/src/index.ts)) -- `@deepseek-ai/dsh-tool-ask-user` — requires `tools` · `userInteraction` ([`packages/ui/tool-ask-user/src/index.ts`](../packages/ui/tool-ask-user/src/index.ts)) +- `@deepseek-ai/dsh-timeout-policy` — requires `tools` ([`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts)) +- `@deepseek-ai/dsh-tool-ask-user` — requires `tools` · `userInteraction` ([`packages/interaction/tool-ask-user/src/index.ts`](../packages/interaction/tool-ask-user/src/index.ts)) - `@deepseek-ai/dsh-tool-subagent-control` — requires `tools` · `subagents` ([`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts)) -- `@deepseek-ai/dsh-user-interaction` ([`packages/ui/user-interaction/src/index.ts`](../packages/ui/user-interaction/src/index.ts)) +- `@deepseek-ai/dsh-user-interaction` ([`packages/interaction/user-interaction/src/index.ts`](../packages/interaction/user-interaction/src/index.ts)) - `@deepseek-ai/dsh-workspace` — requires `storageDomain` · `sessionPersistence` ([`packages/workspace/workspace/src/index.ts`](../packages/workspace/workspace/src/index.ts)) ## Seam packages (not directly loadable) @@ -2636,7 +2636,7 @@ Abstract service classes — a deployment loads a concrete implementation packag - `@deepseek-ai/dsh-fs` — abstract `FileSystem` ([`packages/fs/fs/src/index.ts`](../packages/fs/fs/src/index.ts)) - `@deepseek-ai/dsh-host-directory-picker` — abstract `DirectoryPicker` ([`packages/host/directory-picker/src/index.ts`](../packages/host/directory-picker/src/index.ts)) - `@deepseek-ai/dsh-sandbox` — abstract `SandboxProvider` ([`packages/sandbox/sandbox/src/index.ts`](../packages/sandbox/sandbox/src/index.ts)) -- `@deepseek-ai/dsh-session-persistence` — abstract `SessionPersistence` ([`packages/session-persistence/session-persistence/src/index.ts`](../packages/session-persistence/session-persistence/src/index.ts)) +- `@deepseek-ai/dsh-session-persistence` — abstract `SessionPersistence` ([`packages/session/session-persistence/src/index.ts`](../packages/session/session-persistence/src/index.ts)) - `@deepseek-ai/dsh-session-query` — abstract `SessionQueryService` ([`packages/session-query/session-query/src/index.ts`](../packages/session-query/session-query/src/index.ts)) - `@deepseek-ai/dsh-settings` — abstract `Settings` ([`packages/settings/settings/src/index.ts`](../packages/settings/settings/src/index.ts)) - `@deepseek-ai/dsh-spill` — abstract `SpillStore` ([`packages/spill/spill/src/index.ts`](../packages/spill/spill/src/index.ts)) @@ -2648,10 +2648,10 @@ Abstract service classes — a deployment loads a concrete implementation packag Imported as libraries by other packages; a `cordis.yml` cannot load them. -- `@deepseek-ai/create-sdk` ([`packages/sdk/create-sdk/src/index.ts`](../packages/sdk/create-sdk/src/index.ts)) +- `@deepseek-ai/create-sdk` ([`packages/scaffold/create-sdk/src/index.ts`](../packages/scaffold/create-sdk/src/index.ts)) - `@deepseek-ai/dsh-acp-snapshot` ([`packages/support/acp-snapshot/src/index.ts`](../packages/support/acp-snapshot/src/index.ts)) - `@deepseek-ai/dsh-agent-loop-testkit` ([`packages/support/agent-loop-testkit/src/index.ts`](../packages/support/agent-loop-testkit/src/index.ts)) -- `@deepseek-ai/dsh-app-boot` ([`packages/ui/app-boot/src/index.ts`](../packages/ui/app-boot/src/index.ts)) +- `@deepseek-ai/dsh-app-boot` ([`packages/boot/app-boot/src/index.ts`](../packages/boot/app-boot/src/index.ts)) - `@deepseek-ai/dsh-atomic-write` ([`packages/util/atomic-write/src/index.ts`](../packages/util/atomic-write/src/index.ts)) - `@deepseek-ai/dsh-base` ([`packages/bundle/base/src/index.ts`](../packages/bundle/base/src/index.ts)) - `@deepseek-ai/dsh-brand` ([`packages/util/brand/src/index.ts`](../packages/util/brand/src/index.ts)) @@ -2662,7 +2662,7 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. - `@deepseek-ai/dsh-client-web` ([`packages/client/web/src/index.ts`](../packages/client/web/src/index.ts)) - `@deepseek-ai/dsh-client-web-react` ([`packages/client/web-react/src/index.ts`](../packages/client/web-react/src/index.ts)) - `@deepseek-ai/dsh-environment` ([`packages/util/environment/src/index.ts`](../packages/util/environment/src/index.ts)) -- `@deepseek-ai/dsh-helper` ([`packages/sdk/helper/src/index.ts`](../packages/sdk/helper/src/index.ts)) +- `@deepseek-ai/dsh-helper` ([`packages/scaffold/helper/src/index.ts`](../packages/scaffold/helper/src/index.ts)) - `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts)) - `@deepseek-ai/dsh-jsonrpc-demo` ([`packages/examples/jsonrpc-demo/src/index.ts`](../packages/examples/jsonrpc-demo/src/index.ts)) - `@deepseek-ai/dsh-llm-mock-server` ([`packages/support/llm-mock-server/src/index.ts`](../packages/support/llm-mock-server/src/index.ts)) @@ -2671,13 +2671,13 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. - `@deepseek-ai/dsh-paths` ([`packages/util/paths/src/index.ts`](../packages/util/paths/src/index.ts)) - `@deepseek-ai/dsh-retention` ([`packages/util/retention/src/index.ts`](../packages/util/retention/src/index.ts)) - `@deepseek-ai/dsh-scope` ([`packages/core/scope/src/index.ts`](../packages/core/scope/src/index.ts)) -- `@deepseek-ai/dsh-scripts` ([`packages/sdk/scripts/src/index.ts`](../packages/sdk/scripts/src/index.ts)) -- `@deepseek-ai/dsh-sdk-client` ([`packages/sdk/sdk-client/src/index.ts`](../packages/sdk/sdk-client/src/index.ts)) -- `@deepseek-ai/dsh-sdk-protocol` ([`packages/sdk/sdk-protocol/src/index.ts`](../packages/sdk/sdk-protocol/src/index.ts)) -- `@deepseek-ai/dsh-session-telemetry` ([`packages/telemetry/session-telemetry/src/index.ts`](../packages/telemetry/session-telemetry/src/index.ts)) -- `@deepseek-ai/dsh-session-title-llm` ([`packages/session-title/session-title-llm/src/index.ts`](../packages/session-title/session-title-llm/src/index.ts)) +- `@deepseek-ai/dsh-scripts` ([`packages/scaffold/scripts/src/index.ts`](../packages/scaffold/scripts/src/index.ts)) +- `@deepseek-ai/dsh-sdk-client` ([`packages/scaffold/client/src/index.ts`](../packages/scaffold/client/src/index.ts)) +- `@deepseek-ai/dsh-sdk-protocol` ([`packages/scaffold/protocol/src/index.ts`](../packages/scaffold/protocol/src/index.ts)) +- `@deepseek-ai/dsh-session-telemetry` ([`packages/session/session-telemetry/src/index.ts`](../packages/session/session-telemetry/src/index.ts)) +- `@deepseek-ai/dsh-session-title-llm` ([`packages/session/session-title-llm/src/index.ts`](../packages/session/session-title-llm/src/index.ts)) - `@deepseek-ai/dsh-subagent-inprocess` ([`packages/subagent/subagent-inprocess/src/index.ts`](../packages/subagent/subagent-inprocess/src/index.ts)) -- `@deepseek-ai/dsh-telemetry` ([`packages/sdk/telemetry/src/index.ts`](../packages/sdk/telemetry/src/index.ts)) +- `@deepseek-ai/dsh-telemetry` ([`packages/scaffold/telemetry/src/index.ts`](../packages/scaffold/telemetry/src/index.ts)) - `@deepseek-ai/dsh-timeout` ([`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts)) - `@deepseek-ai/dsh-type-meta` ([`packages/typert/type-meta/src/index.ts`](../packages/typert/type-meta/src/index.ts)) - `@deepseek-ai/dsh-typert-generator` ([`packages/typert/generator/src/index.ts`](../packages/typert/generator/src/index.ts)) diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index 7015d39bc8..2c99c451d2 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -325,7 +325,7 @@ Ask composed answerers for one decision. Return an outcome to claim the request Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [ApprovalService](../core-data-structures/approval.md) · [Scoped](../core-data-structures/scope.md) -Source: [`packages/ui/user-approval/src/index.ts:30`](../../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:30`](../../packages/interaction/user-approval/src/index.ts) ## `commands/*` @@ -343,7 +343,7 @@ A command was registered or unregistered. This is an unfiltered registry notific 'commands/change'(): void ``` -Source: [`packages/ui/commands/src/index.ts:172`](../../packages/ui/commands/src/index.ts) +Source: [`packages/interaction/commands/src/index.ts:172`](../../packages/interaction/commands/src/index.ts) ## `credentials/*` @@ -822,7 +822,7 @@ Transform one outbound record before it reaches the backend. This waterfall is t 'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord ``` -Source: [`packages/telemetry/session-telemetry/src/index.ts:43`](../../packages/telemetry/session-telemetry/src/index.ts) +Source: [`packages/session/session-telemetry/src/index.ts:43`](../../packages/session/session-telemetry/src/index.ts) ## `tools/*` diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 8af0e6ca30..56a0d0d2e6 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -262,7 +262,7 @@ overrideOf(session: Session): ApprovalPolicy | undefined Types: [Agent](../core-data-structures/core.md) · [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalPolicy](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [Session](../core-data-structures/session.md) -Source: [`packages/ui/user-approval/src/index.ts:193`](../../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:193`](../../packages/interaction/user-approval/src/index.ts) ## `ctx.bash` — `BashExecutor` (abstract seam) @@ -451,7 +451,7 @@ async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise Promise Types: [Session](../core-data-structures/session.md) · [SessionTitleProvider](../core-data-structures/session-title.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md) -Source: [`packages/session-title/session-title/src/index.ts:261`](../../packages/session-title/session-title/src/index.ts) +Source: [`packages/session/session-title/src/index.ts:261`](../../packages/session/session-title/src/index.ts) ## `ctx.settings` — `Settings` (abstract seam) @@ -2473,7 +2473,7 @@ flush?(): void abstract shutdown(): Promise ``` -Source: [`packages/telemetry/session-telemetry/src/index.ts:140`](../../packages/telemetry/session-telemetry/src/index.ts) +Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts) ## `ctx.tokenMeter` — `TokenMeterService` @@ -2745,7 +2745,7 @@ async ask(request: AskUserQuestionRequest): Promise Types: [AskUserQuestionAnswer](../core-data-structures/user-interaction.md) · [AskUserQuestionRequest](../core-data-structures/user-interaction.md) · [UserInteractionProvider](../core-data-structures/user-interaction.md) -Source: [`packages/ui/user-interaction/src/index.ts:51`](../../packages/ui/user-interaction/src/index.ts) +Source: [`packages/interaction/user-interaction/src/index.ts:51`](../../packages/interaction/user-interaction/src/index.ts) ## `ctx.web` — `WebService` diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 9699870ecc..f5ea3eba36 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -10,18 +10,18 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:182`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent/created` | `emit` | [`packages/core/agent/src/types.ts:158`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`goal-session`](../packages/goal/goal-session) | | `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:167`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | -| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:289`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`session-telemetry`](../packages/telemetry/session-telemetry) | +| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:289`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`session-telemetry`](../packages/session/session-telemetry) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/types.ts:196`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/types.ts:204`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/types.ts:185`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-session`](../packages/goal/goal-session) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/types.ts:230`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/types.ts:230`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) | | `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:243`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) | | `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:259`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry) | | `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:216`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | -| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:177`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`headless`](../packages/bundle/headless), [`jsonrpc`](../packages/ui/jsonrpc) | +| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:177`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`headless`](../packages/bundle/headless), `server` | | `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:277`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | -| `approval/request` | `waterfall` | [`packages/ui/user-approval/src/index.ts:30`](../packages/ui/user-approval/src/index.ts) | [`user-approval`](../packages/ui/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` | -| `commands/change` | `emit` | [`packages/ui/commands/src/index.ts:172`](../packages/ui/commands/src/index.ts) | [`commands`](../packages/ui/commands) (`events.dispatch`) | `apiproxy` | +| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/index.ts:30`](../packages/interaction/user-approval/src/index.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `apiproxy` | +| `commands/change` | `emit` | [`packages/interaction/commands/src/index.ts:172`](../packages/interaction/commands/src/index.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `apiproxy` | | `credentials/updated` | `emit` | [`packages/credentials/credentials/src/index.ts:67`](../packages/credentials/credentials/src/index.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) | | `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) | | `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:64`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) | @@ -29,24 +29,24 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:56`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`waterfall`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`waterfall`) | [`fs-policy`](../packages/fs/fs-policy) | | `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-session`](../packages/goal/goal-session) | | `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/index.ts:73`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | `apiproxy`, [`llm`](../packages/llm/llm) | -| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:62`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/support/llm-replay), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`session-title`](../packages/session-title/session-title) | -| `session/created` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | -| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-title`](../packages/session-title/session-title) | -| `session/event` | `emit` | [`packages/core/session/src/index.ts:96`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`jsonrpc`](../packages/ui/jsonrpc), [`loader-smoke`](../packages/support/loader-smoke), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection`](../packages/session-projection/session-projection), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`session-telemetry`](../packages/telemetry/session-telemetry), [`session-telemetry-otel`](../packages/telemetry/session-telemetry-otel), [`session-title`](../packages/session-title/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) | -| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:105`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session-persistence/session-persistence), [`session-telemetry`](../packages/telemetry/session-telemetry) | +| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:62`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`llm-replay`](../packages/support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) | +| `session/created` | `emit` | [`packages/core/session/src/index.ts:74`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | `apiproxy`, [`compact`](../packages/compact/compact), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/interaction/permission), [`plan-mode`](../packages/plan/plan-mode), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | +| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`session-persistence`](../packages/session/session-persistence), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title) | +| `session/event` | `emit` | [`packages/core/session/src/index.ts:96`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-loop`](../packages/core/agent-loop), `apiproxy`, [`compact`](../packages/compact/compact), [`compact-basic`](../packages/compact/compact-basic), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/support/loader-smoke), `server`, [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-telemetry-otel`](../packages/session/session-telemetry-otel), [`session-title`](../packages/session/session-title), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-context`](../packages/context/workspace-context) | +| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:105`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence`](../packages/session/session-persistence), [`session-telemetry`](../packages/session/session-telemetry) | | `settings/document-updated` | `emit` | [`packages/settings/settings/src/index.ts:170`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `apiproxy` | | `settings/updated` | `emit` | [`packages/settings/settings/src/index.ts:157`](../packages/settings/settings/src/index.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) | | `skills/change` | `emit` | [`packages/skill/skill/src/index.ts:283`](../packages/skill/skill/src/index.ts) | [`skill`](../packages/skill/skill) (`events.dispatch`) | - | -| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:162`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`jsonrpc`](../packages/ui/jsonrpc), [`subagent`](../packages/subagent/subagent) | +| `subagent/end` | `emit` | [`packages/subagent/subagent/src/index.ts:162`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), `server`, [`subagent`](../packages/subagent/subagent) | | `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:136`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`emit`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | | `subagent/provider-removed` | `emit` | [`packages/subagent/subagent/src/index.ts:142`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`subagent`](../packages/subagent/subagent), [`tool-subagent`](../packages/subagent/tool-subagent) | | `subagent/start` | `emit` | [`packages/subagent/subagent/src/index.ts:153`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`subagent`](../packages/subagent/subagent) | | `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:29`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`system-prompt`](../packages/core/system-prompt) | | `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:35`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - | -| `telemetry/record` | `waterfall` | [`packages/telemetry/session-telemetry/src/index.ts:43`](../packages/telemetry/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/telemetry/session-telemetry) (`waterfall`) | - | +| `telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:43`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - | | `tools/change` | `emit` | [`packages/core/tools/src/index.ts:191`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`emit`) | - | | `tools/code-dispatch-log` | `waterfall` | [`packages/core/tools/src/index.ts:173`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`spill-policy`](../packages/spill/spill-policy) | -| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:148`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`timeout-policy`](../packages/timeout/timeout-policy) | +| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:148`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`timeout-policy`](../packages/guard/timeout-policy) | | `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:160`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) | | `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:137`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-tasks`](../packages/tasks/tool-tasks) | | `tools/result` | `emit` | [`packages/core/tools/src/index.ts:181`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`events.dispatch`) | [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`workspace-context`](../packages/context/workspace-context) | @@ -64,7 +64,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `commands/changed` | `runtime` (`emit`) | `ui-command` | | `connection/reset` | `runtime` (`emit`) | `ui-command`, `ui-models`, `ui-permission`, `ui-settings-general` | | `credentials/changed` | `runtime` (`emit`) | `ui-models` | -| `internal/dispatch` | - | [`commands`](../packages/ui/commands), [`compact`](../packages/compact/compact), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`pty-local`](../packages/pty/pty-local), `runtime`, [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`workflow`](../packages/workflow/workflow) | +| `internal/dispatch` | - | [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/interaction/permission), [`plan-mode`](../packages/plan/plan-mode), [`pty-local`](../packages/pty/pty-local), `runtime`, [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workflow`](../packages/workflow/workflow) | | `internal/plugin` | - | `hmr`, `loader`, [`lsp-local`](../packages/lsp/lsp-local), `modules`, `webserver` | | `internal/service` | - | `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent) | diff --git a/docs/module-graph.md b/docs/module-graph.md index 2e71473379..6f47c36e97 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -93,41 +93,22 @@ flowchart TD pkg_spill_local["spill-local"] pkg_spill_policy["spill-policy"] end - subgraph group_timeout["packages/timeout"] - pkg_timeout_policy["timeout-policy"] - end subgraph group_todo["packages/todo"] pkg_tool_todo["tool-todo"] end subgraph group_plan["packages/plan"] pkg_plan_mode["plan-mode"] end - subgraph group_cordis["packages/cordis"] - pkg_repository_plugin["repository-plugin"] - pkg_tool_cordis["tool-cordis"] - end subgraph group_hooks["packages/hooks"] pkg_hook_protocol["hook-protocol"] pkg_hooks_claude["hooks-claude"] pkg_hooks_codex["hooks-codex"] end - subgraph group_session_persistence["packages/session-persistence"] - pkg_session_checkpoint_policy["session-checkpoint-policy"] - pkg_session_persistence["session-persistence"] - pkg_session_persistence_jsonl["session-persistence-jsonl"] - pkg_session_persistence_sqlite["session-persistence-sqlite"] - end subgraph group_session_query["packages/session-query"] pkg_session_query["session-query"] pkg_session_query_sqlite["session-query-sqlite"] pkg_tool_session_query["tool-session-query"] end - subgraph group_session_title["packages/session-title"] - pkg_session_title["session-title"] - pkg_session_title_all_messages_llm["session-title-all-messages-llm"] - pkg_session_title_first_message_llm["session-title-first-message-llm"] - pkg_session_title_llm["session-title-llm"] - end subgraph group_support["packages/support"] pkg_acp_snapshot["acp-snapshot"] pkg_agent_loop_testkit["agent-loop-testkit"] @@ -139,19 +120,13 @@ flowchart TD subgraph group_acp["packages/acp"] pkg_acp["acp"] end - subgraph group_ui["packages/ui"] - pkg_app_boot["app-boot"] - pkg_commands["commands"] - pkg_jsonrpc["jsonrpc"] - pkg_permission["permission"] - pkg_tool_ask_user["tool-ask-user"] - pkg_user_approval["user-approval"] - pkg_user_interaction["user-interaction"] - end subgraph group_api["packages/api"] pkg_api_gateway["api-gateway"] pkg_api_remotes["api-remotes"] end + subgraph group_boot["packages/boot"] + pkg_app_boot["app-boot"] + end subgraph group_bundle["packages/bundle"] pkg_base["base"] pkg_headless["headless"] @@ -219,6 +194,7 @@ flowchart TD end subgraph group_guard["packages/guard"] pkg_repeat_tool_guard["repeat-tool-guard"] + pkg_timeout_policy["timeout-policy"] end subgraph group_host["packages/host"] pkg_frontend_static["frontend-static"] @@ -229,6 +205,13 @@ flowchart TD pkg_host_directory_picker_native["host-directory-picker-native"] pkg_host_webserver["host-webserver"] end + subgraph group_interaction["packages/interaction"] + pkg_commands["commands"] + pkg_permission["permission"] + pkg_tool_ask_user["tool-ask-user"] + pkg_user_approval["user-approval"] + pkg_user_interaction["user-interaction"] + end subgraph group_lsp["packages/lsp"] pkg_lsp["lsp"] pkg_lsp_local["lsp-local"] @@ -248,16 +231,31 @@ flowchart TD pkg_sandbox_local["sandbox-local"] pkg_sandbox_policy["sandbox-policy"] end - subgraph group_sdk["packages/sdk"] + subgraph group_scaffold["packages/scaffold"] pkg_helper["helper"] + pkg_jsonrpc["jsonrpc"] pkg_scripts["scripts"] pkg_sdk_client["sdk-client"] pkg_sdk_protocol["sdk-protocol"] pkg_telemetry["telemetry"] end - subgraph group_session_projection["packages/session-projection"] + subgraph group_self_modification["packages/self-modification"] + pkg_repository_plugin["repository-plugin"] + pkg_tool_cordis["tool-cordis"] + end + subgraph group_session["packages/session"] + pkg_session_checkpoint_policy["session-checkpoint-policy"] + pkg_session_persistence["session-persistence"] + pkg_session_persistence_jsonl["session-persistence-jsonl"] + pkg_session_persistence_sqlite["session-persistence-sqlite"] pkg_session_projection["session-projection"] pkg_session_projection_cache["session-projection-cache"] + pkg_session_telemetry["session-telemetry"] + pkg_session_telemetry_otel["session-telemetry-otel"] + pkg_session_title["session-title"] + pkg_session_title_all_messages_llm["session-title-all-messages-llm"] + pkg_session_title_first_message_llm["session-title-first-message-llm"] + pkg_session_title_llm["session-title-llm"] end subgraph group_settings["packages/settings"] pkg_settings["settings"] @@ -278,10 +276,6 @@ flowchart TD pkg_tasks_local["tasks-local"] pkg_tool_tasks["tool-tasks"] end - subgraph group_telemetry["packages/telemetry"] - pkg_session_telemetry["session-telemetry"] - pkg_session_telemetry_otel["session-telemetry-otel"] - end subgraph group_typert["packages/typert"] pkg_type_meta["type-meta"] pkg_typert_generator["typert-generator"] @@ -457,10 +451,6 @@ flowchart TD pkg_spill --> pkg_invariants pkg_spill --> pkg_llm pkg_spill --> pkg_session - pkg_session_persistence --> pkg_brand - pkg_session_persistence --> pkg_invariants - pkg_session_persistence --> pkg_session - pkg_session_persistence --> pkg_timeout pkg_acp_snapshot --> pkg_invariants pkg_acp_snapshot --> pkg_session pkg_app_boot --> pkg_environment @@ -504,6 +494,10 @@ flowchart TD pkg_sandbox_local --> pkg_invariants pkg_sandbox_local --> pkg_llm pkg_sandbox_local --> pkg_sandbox + pkg_session_persistence --> pkg_brand + pkg_session_persistence --> pkg_invariants + pkg_session_persistence --> pkg_session + pkg_session_persistence --> pkg_timeout pkg_session_projection --> pkg_invariants pkg_session_projection --> pkg_session pkg_llm_retry --> pkg_agent @@ -551,17 +545,6 @@ flowchart TD pkg_hook_protocol --> pkg_bash pkg_hook_protocol --> pkg_invariants pkg_hook_protocol --> pkg_session - pkg_session_persistence_jsonl --> pkg_invariants - pkg_session_persistence_jsonl --> pkg_session - pkg_session_persistence_jsonl --> pkg_session_persistence - pkg_session_persistence_sqlite --> pkg_invariants - pkg_session_persistence_sqlite --> pkg_session - pkg_session_persistence_sqlite --> pkg_session_persistence - pkg_session_title --> pkg_brand - pkg_session_title --> pkg_invariants - pkg_session_title --> pkg_llm - pkg_session_title --> pkg_session - pkg_session_title --> pkg_session_projection pkg_llm_replay --> pkg_compact pkg_llm_replay --> pkg_invariants pkg_llm_replay --> pkg_llm @@ -570,21 +553,6 @@ flowchart TD pkg_loader_smoke --> pkg_invariants pkg_loader_smoke --> pkg_llm pkg_loader_smoke --> pkg_session - pkg_commands --> pkg_agent - pkg_commands --> pkg_brand - pkg_commands --> pkg_invariants - pkg_commands --> pkg_scope - pkg_commands --> pkg_session - pkg_user_approval --> pkg_agent - pkg_user_approval --> pkg_brand - pkg_user_approval --> pkg_invariants - pkg_user_approval --> pkg_llm - pkg_user_approval --> pkg_scope - pkg_user_approval --> pkg_session - pkg_user_approval --> pkg_system_prompt - pkg_user_interaction --> pkg_agent - pkg_user_interaction --> pkg_invariants - pkg_user_interaction --> pkg_llm pkg_headless --> pkg_agent pkg_headless --> pkg_host_apiproxy pkg_headless --> pkg_host_webserver @@ -614,6 +582,21 @@ flowchart TD pkg_host_directory_picker_native --> pkg_client_ui_slots pkg_host_directory_picker_native --> pkg_client_ui_workspace pkg_host_directory_picker_native --> pkg_invariants + pkg_commands --> pkg_agent + pkg_commands --> pkg_brand + pkg_commands --> pkg_invariants + pkg_commands --> pkg_scope + pkg_commands --> pkg_session + pkg_user_approval --> pkg_agent + pkg_user_approval --> pkg_brand + pkg_user_approval --> pkg_invariants + pkg_user_approval --> pkg_llm + pkg_user_approval --> pkg_scope + pkg_user_approval --> pkg_session + pkg_user_approval --> pkg_system_prompt + pkg_user_interaction --> pkg_agent + pkg_user_interaction --> pkg_invariants + pkg_user_interaction --> pkg_llm pkg_lsp_local --> pkg_brand pkg_lsp_local --> pkg_fs pkg_lsp_local --> pkg_invariants @@ -631,18 +614,29 @@ flowchart TD pkg_sandbox_policy --> pkg_system_prompt pkg_scripts --> pkg_app_boot pkg_scripts --> pkg_invariants + pkg_session_persistence_jsonl --> pkg_invariants + pkg_session_persistence_jsonl --> pkg_session + pkg_session_persistence_jsonl --> pkg_session_persistence + pkg_session_persistence_sqlite --> pkg_invariants + pkg_session_persistence_sqlite --> pkg_session + pkg_session_persistence_sqlite --> pkg_session_persistence pkg_session_projection_cache --> pkg_invariants pkg_session_projection_cache --> pkg_session pkg_session_projection_cache --> pkg_session_persistence pkg_session_projection_cache --> pkg_session_projection pkg_session_projection_cache --> pkg_storage_domain + pkg_session_telemetry --> pkg_agent + pkg_session_telemetry --> pkg_invariants + pkg_session_telemetry --> pkg_session + pkg_session_title --> pkg_brand + pkg_session_title --> pkg_invariants + pkg_session_title --> pkg_llm + pkg_session_title --> pkg_session + pkg_session_title --> pkg_session_projection pkg_tasks --> pkg_agent pkg_tasks --> pkg_brand pkg_tasks --> pkg_invariants pkg_tasks --> pkg_session - pkg_session_telemetry --> pkg_agent - pkg_session_telemetry --> pkg_invariants - pkg_session_telemetry --> pkg_session pkg_workflow --> pkg_agent pkg_workflow --> pkg_brand pkg_workflow --> pkg_invariants @@ -694,24 +688,10 @@ flowchart TD pkg_session_query --> pkg_session pkg_session_query --> pkg_session_persistence pkg_session_query --> pkg_session_title - pkg_session_title_llm --> pkg_invariants - pkg_session_title_llm --> pkg_llm - pkg_session_title_llm --> pkg_session - pkg_session_title_llm --> pkg_session_title - pkg_session_title_llm --> pkg_timeout pkg_acp --> pkg_agent pkg_acp --> pkg_invariants pkg_acp --> pkg_session pkg_acp --> pkg_user_approval - pkg_permission --> pkg_bash - pkg_permission --> pkg_commands - pkg_permission --> pkg_invariants - pkg_permission --> pkg_sandbox - pkg_permission --> pkg_sandbox_policy - pkg_permission --> pkg_session - pkg_permission --> pkg_session_projection - pkg_permission --> pkg_settings - pkg_permission --> pkg_user_approval pkg_api_remotes --> pkg_agent pkg_api_remotes --> pkg_goal pkg_api_remotes --> pkg_invariants @@ -732,6 +712,15 @@ flowchart TD pkg_host_directory_picker_auto --> pkg_host_directory_picker_native pkg_host_directory_picker_auto --> pkg_host_webserver pkg_host_directory_picker_auto --> pkg_invariants + pkg_permission --> pkg_bash + pkg_permission --> pkg_commands + pkg_permission --> pkg_invariants + pkg_permission --> pkg_sandbox + pkg_permission --> pkg_sandbox_policy + pkg_permission --> pkg_session + pkg_permission --> pkg_session_projection + pkg_permission --> pkg_settings + pkg_permission --> pkg_user_approval pkg_pty_local --> pkg_agent pkg_pty_local --> pkg_invariants pkg_pty_local --> pkg_pty @@ -739,6 +728,11 @@ flowchart TD pkg_pty_local --> pkg_sandbox_policy pkg_pty_local --> pkg_session pkg_pty_local --> pkg_subprocess + pkg_session_title_llm --> pkg_invariants + pkg_session_title_llm --> pkg_llm + pkg_session_title_llm --> pkg_session + pkg_session_title_llm --> pkg_session_title + pkg_session_title_llm --> pkg_timeout pkg_tasks_local --> pkg_agent pkg_tasks_local --> pkg_invariants pkg_tasks_local --> pkg_tasks @@ -820,10 +814,6 @@ flowchart TD pkg_spill_policy --> pkg_session pkg_spill_policy --> pkg_spill pkg_spill_policy --> pkg_tools - pkg_timeout_policy --> pkg_invariants - pkg_timeout_policy --> pkg_llm - pkg_timeout_policy --> pkg_timeout - pkg_timeout_policy --> pkg_tools pkg_tool_todo --> pkg_agent pkg_tool_todo --> pkg_invariants pkg_tool_todo --> pkg_session @@ -838,9 +828,6 @@ flowchart TD pkg_plan_mode --> pkg_system_prompt pkg_plan_mode --> pkg_tools pkg_plan_mode --> pkg_user_interaction - pkg_tool_cordis --> pkg_invariants - pkg_tool_cordis --> pkg_scope - pkg_tool_cordis --> pkg_tools pkg_hooks_codex --> pkg_agent pkg_hooks_codex --> pkg_hook_protocol pkg_hooks_codex --> pkg_invariants @@ -848,12 +835,6 @@ flowchart TD pkg_hooks_codex --> pkg_session pkg_hooks_codex --> pkg_session_persistence pkg_hooks_codex --> pkg_tools - pkg_session_checkpoint_policy --> pkg_agent - pkg_session_checkpoint_policy --> pkg_invariants - pkg_session_checkpoint_policy --> pkg_llm - pkg_session_checkpoint_policy --> pkg_session - pkg_session_checkpoint_policy --> pkg_session_persistence - pkg_session_checkpoint_policy --> pkg_tools pkg_session_query_sqlite --> pkg_invariants pkg_session_query_sqlite --> pkg_session pkg_session_query_sqlite --> pkg_session_persistence @@ -865,26 +846,12 @@ flowchart TD pkg_tool_session_query --> pkg_system_prompt pkg_tool_session_query --> pkg_timeout pkg_tool_session_query --> pkg_tools - pkg_session_title_all_messages_llm --> pkg_invariants - pkg_session_title_all_messages_llm --> pkg_llm - pkg_session_title_all_messages_llm --> pkg_session - pkg_session_title_all_messages_llm --> pkg_session_title - pkg_session_title_all_messages_llm --> pkg_session_title_llm - pkg_session_title_first_message_llm --> pkg_invariants - pkg_session_title_first_message_llm --> pkg_llm - pkg_session_title_first_message_llm --> pkg_session - pkg_session_title_first_message_llm --> pkg_session_title - pkg_session_title_first_message_llm --> pkg_session_title_llm pkg_agent_loop_testkit --> pkg_agent pkg_agent_loop_testkit --> pkg_invariants pkg_agent_loop_testkit --> pkg_llm pkg_agent_loop_testkit --> pkg_session pkg_agent_loop_testkit --> pkg_system_prompt pkg_agent_loop_testkit --> pkg_tools - pkg_tool_ask_user --> pkg_agent - pkg_tool_ask_user --> pkg_invariants - pkg_tool_ask_user --> pkg_tools - pkg_tool_ask_user --> pkg_user_interaction pkg_client_ui_command --> pkg_client_connection pkg_client_ui_command --> pkg_client_locale pkg_client_ui_command --> pkg_client_runtime @@ -929,6 +896,14 @@ flowchart TD pkg_repeat_tool_guard --> pkg_agent pkg_repeat_tool_guard --> pkg_invariants pkg_repeat_tool_guard --> pkg_tools + pkg_timeout_policy --> pkg_invariants + pkg_timeout_policy --> pkg_llm + pkg_timeout_policy --> pkg_timeout + pkg_timeout_policy --> pkg_tools + pkg_tool_ask_user --> pkg_agent + pkg_tool_ask_user --> pkg_invariants + pkg_tool_ask_user --> pkg_tools + pkg_tool_ask_user --> pkg_user_interaction pkg_tool_lsp --> pkg_invariants pkg_tool_lsp --> pkg_llm pkg_tool_lsp --> pkg_lsp @@ -952,13 +927,15 @@ flowchart TD pkg_tool_pty --> pkg_system_prompt pkg_tool_pty --> pkg_tasks pkg_tool_pty --> pkg_tools - pkg_tool_tasks --> pkg_agent - pkg_tool_tasks --> pkg_invariants - pkg_tool_tasks --> pkg_llm - pkg_tool_tasks --> pkg_retention - pkg_tool_tasks --> pkg_system_prompt - pkg_tool_tasks --> pkg_tasks - pkg_tool_tasks --> pkg_tools + pkg_tool_cordis --> pkg_invariants + pkg_tool_cordis --> pkg_scope + pkg_tool_cordis --> pkg_tools + pkg_session_checkpoint_policy --> pkg_agent + pkg_session_checkpoint_policy --> pkg_invariants + pkg_session_checkpoint_policy --> pkg_llm + pkg_session_checkpoint_policy --> pkg_session + pkg_session_checkpoint_policy --> pkg_session_persistence + pkg_session_checkpoint_policy --> pkg_tools pkg_session_telemetry_otel --> pkg_brand pkg_session_telemetry_otel --> pkg_command_feedback pkg_session_telemetry_otel --> pkg_invariants @@ -966,6 +943,23 @@ flowchart TD pkg_session_telemetry_otel --> pkg_paths pkg_session_telemetry_otel --> pkg_session pkg_session_telemetry_otel --> pkg_session_telemetry + pkg_session_title_all_messages_llm --> pkg_invariants + pkg_session_title_all_messages_llm --> pkg_llm + pkg_session_title_all_messages_llm --> pkg_session + pkg_session_title_all_messages_llm --> pkg_session_title + pkg_session_title_all_messages_llm --> pkg_session_title_llm + pkg_session_title_first_message_llm --> pkg_invariants + pkg_session_title_first_message_llm --> pkg_llm + pkg_session_title_first_message_llm --> pkg_session + pkg_session_title_first_message_llm --> pkg_session_title + pkg_session_title_first_message_llm --> pkg_session_title_llm + pkg_tool_tasks --> pkg_agent + pkg_tool_tasks --> pkg_invariants + pkg_tool_tasks --> pkg_llm + pkg_tool_tasks --> pkg_retention + pkg_tool_tasks --> pkg_system_prompt + pkg_tool_tasks --> pkg_tasks + pkg_tool_tasks --> pkg_tools pkg_tool_workflow --> pkg_agent pkg_tool_workflow --> pkg_invariants pkg_tool_workflow --> pkg_llm @@ -1028,10 +1022,6 @@ flowchart TD pkg_tool_subagent_report --> pkg_llm pkg_tool_subagent_report --> pkg_subagent pkg_tool_subagent_report --> pkg_tools - pkg_repository_plugin --> pkg_invariants - pkg_repository_plugin --> pkg_mcp_client - pkg_repository_plugin --> pkg_paths - pkg_repository_plugin --> pkg_skill_local pkg_hooks_claude --> pkg_agent pkg_hooks_claude --> pkg_hook_protocol pkg_hooks_claude --> pkg_invariants @@ -1091,6 +1081,10 @@ flowchart TD pkg_sdk_protocol --> pkg_llm pkg_sdk_protocol --> pkg_session pkg_sdk_protocol --> pkg_subagent + pkg_repository_plugin --> pkg_invariants + pkg_repository_plugin --> pkg_mcp_client + pkg_repository_plugin --> pkg_paths + pkg_repository_plugin --> pkg_skill_local pkg_tool_ralph --> pkg_agent pkg_tool_ralph --> pkg_invariants pkg_tool_ralph --> pkg_llm @@ -1121,14 +1115,6 @@ flowchart TD pkg_subagent_spawn --> pkg_invariants pkg_subagent_spawn --> pkg_subagent pkg_subagent_spawn --> pkg_subagent_inprocess - pkg_jsonrpc --> pkg_agent - pkg_jsonrpc --> pkg_invariants - pkg_jsonrpc --> pkg_llm - pkg_jsonrpc --> pkg_llm_deepseek - pkg_jsonrpc --> pkg_scope - pkg_jsonrpc --> pkg_sdk_protocol - pkg_jsonrpc --> pkg_session - pkg_jsonrpc --> pkg_subagent pkg_agent_spine_demo --> pkg_agent pkg_agent_spine_demo --> pkg_agent_loop pkg_agent_spine_demo --> pkg_bash_env @@ -1151,6 +1137,14 @@ flowchart TD pkg_agent_spine_demo --> pkg_tool_tasks pkg_agent_spine_demo --> pkg_tools pkg_agent_spine_demo --> pkg_workspace_context + pkg_jsonrpc --> pkg_agent + pkg_jsonrpc --> pkg_invariants + pkg_jsonrpc --> pkg_llm + pkg_jsonrpc --> pkg_llm_deepseek + pkg_jsonrpc --> pkg_scope + pkg_jsonrpc --> pkg_sdk_protocol + pkg_jsonrpc --> pkg_session + pkg_jsonrpc --> pkg_subagent pkg_sdk_client --> pkg_invariants pkg_sdk_client --> pkg_llm pkg_sdk_client --> pkg_sdk_protocol @@ -1211,8 +1205,8 @@ flowchart TD | [`credentials`](../packages/credentials/credentials) | `credentials` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) | | [`subprocess-e2b`](../packages/e2b/subprocess-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`frontend-static`](../packages/host/frontend-static) | `host` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | -| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess) | -| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) | +| [`helper`](../packages/scaffold/helper) | `scaffold` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess) | +| [`telemetry`](../packages/scaffold/telemetry) | `scaffold` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) | | [`settings`](../packages/settings/settings) | `settings` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) | | [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) | | [`storage-json`](../packages/storage/storage-json) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) | @@ -1244,9 +1238,8 @@ flowchart TD | [`web-search-exa`](../packages/web/web-search-exa) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | | [`web-search-perplexity`](../packages/web/web-search-perplexity) | `web` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`web`](../packages/web/web) | | [`spill`](../packages/spill/spill) | `spill` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`session-persistence`](../packages/session-persistence/session-persistence) | `session-persistence` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | -| [`app-boot`](../packages/ui/app-boot) | `ui` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`system-prompt`](../packages/core/system-prompt) | +| [`app-boot`](../packages/boot/app-boot) | `boot` | [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`system-prompt`](../packages/core/system-prompt) | | [`client-ui-question`](../packages/client/ui-question) | `client` | [`client-locale`](../packages/client/locale), [`invariants`](../packages/support/invariants) | | [`client-ui-settings-general`](../packages/client/ui-settings-general) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web-react`](../packages/client/web-react), [`invariants`](../packages/support/invariants) | | [`client-ui-sidebar`](../packages/client/ui-sidebar) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | @@ -1255,10 +1248,11 @@ flowchart TD | [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | | [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | | [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) | -| [`session-projection`](../packages/session-projection/session-projection) | `session-projection` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | +| [`session-persistence`](../packages/session/session-persistence) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | +| [`session-projection`](../packages/session/session-projection) | `session` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | | [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) | -| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection) | -| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`type-meta`](../packages/typert/type-meta) | +| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | +| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`type-meta`](../packages/typert/type-meta) | | [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pwsh-local`](../packages/bash/pwsh-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) | @@ -1267,14 +1261,8 @@ flowchart TD | [`web-search-deepseek`](../packages/web/web-search-deepseek) | `web` | [`agent`](../packages/core/agent), [`credentials`](../packages/credentials/credentials), [`environment`](../packages/util/environment), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`web`](../packages/web/web) | | [`spill-local`](../packages/spill/spill-local) | `spill` | [`invariants`](../packages/support/invariants), [`spill`](../packages/spill/spill) | | [`hook-protocol`](../packages/hooks/hook-protocol) | `hooks` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | -| [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | -| [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | `session-persistence` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence) | -| [`session-title`](../packages/session-title/session-title) | `session-title` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection) | | [`llm-replay`](../packages/support/llm-replay) | `support` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`commands`](../packages/ui/commands) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session) | -| [`user-approval`](../packages/ui/user-approval) | `ui` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | -| [`user-interaction`](../packages/ui/user-interaction) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`host-apiproxy`](../packages/host/apiproxy), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | | [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/support/invariants) | | [`time-context`](../packages/context/time-context) | `context` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | @@ -1282,55 +1270,55 @@ flowchart TD | [`fs-e2b`](../packages/e2b/fs-e2b) | `e2b` | [`e2b`](../packages/e2b/e2b), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) | | [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) | | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) | +| [`commands`](../packages/interaction/commands) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session) | +| [`user-approval`](../packages/interaction/user-approval) | `interaction` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | +| [`user-interaction`](../packages/interaction/user-interaction) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | | [`lsp-local`](../packages/lsp/lsp-local) | `lsp` | [`brand`](../packages/util/brand), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`pty`](../packages/pty/pty) | `pty` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) | | [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) | -| [`scripts`](../packages/sdk/scripts) | `sdk` | [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants) | -| [`session-projection-cache`](../packages/session-projection/session-projection-cache) | `session-projection` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection`](../packages/session-projection/session-projection), [`storage-domain`](../packages/storage/storage-domain) | +| [`scripts`](../packages/scaffold/scripts) | `scaffold` | [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/support/invariants) | +| [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | +| [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | `session` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) | +| [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain) | +| [`session-telemetry`](../packages/session/session-telemetry) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | +| [`session-title`](../packages/session/session-title) | `session` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) | | [`tasks`](../packages/tasks/tasks) | `tasks` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | -| [`session-telemetry`](../packages/telemetry/session-telemetry) | `telemetry` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | | [`workflow`](../packages/workflow/workflow) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | -| [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) | -| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/ui/user-approval) | -| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/ui/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | +| [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) | +| [`tools`](../packages/core/tools) | `core` | [`agent`](../packages/core/agent), [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`user-approval`](../packages/interaction/user-approval) | +| [`command-goal`](../packages/goal/command-goal) | `goal` | [`commands`](../packages/interaction/commands), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | | [`goal-session`](../packages/goal/goal-session) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) | | [`bash-sandbox`](../packages/bash/bash-sandbox) | `bash` | [`bash`](../packages/bash/bash), [`bash-local`](../packages/bash/bash-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | | [`fs-sandbox`](../packages/fs/fs-sandbox) | `fs` | [`fs`](../packages/fs/fs), [`fs-local`](../packages/fs/fs-local), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy) | -| [`command-compact`](../packages/compact/command-compact) | `compact` | [`commands`](../packages/ui/commands), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants) | +| [`command-compact`](../packages/compact/command-compact) | `compact` | [`commands`](../packages/interaction/commands), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants) | | [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | `compact` | [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-title`](../packages/session-title/session-title) | -| [`session-title-llm`](../packages/session-title/session-title-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`timeout`](../packages/util/timeout) | -| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) | -| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`settings`](../packages/settings/settings), [`user-approval`](../packages/ui/user-approval) | -| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`typert-registry`](../packages/typert/registry) | +| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-title`](../packages/session/session-title) | +| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/interaction/user-approval) | +| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`typert-registry`](../packages/typert/registry) | | [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`token-meter`](../packages/llm/token-meter) | -| [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | +| [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | +| [`permission`](../packages/interaction/permission) | `interaction` | [`bash`](../packages/bash/bash), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`user-approval`](../packages/interaction/user-approval) | | [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess) | +| [`session-title-llm`](../packages/session/session-title-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`timeout`](../packages/util/timeout) | | [`tasks-local`](../packages/tasks/tasks-local) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tasks`](../packages/tasks/tasks), [`timeout`](../packages/util/timeout) | -| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | +| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`bash-env`](../packages/bash/bash-env) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | -| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | +| [`bash-env`](../packages/bash/bash-env) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | +| [`tool-fs`](../packages/fs/tool-fs) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-fs-search`](../packages/fs/tool-fs-search) | `fs` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`tools`](../packages/core/tools) | | [`tool-skill`](../packages/skill/tool-skill) | `skill` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`skill`](../packages/skill/skill), [`tools`](../packages/core/tools) | | [`compact-basic`](../packages/compact/compact-basic) | `compact` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | -| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-projection`](../packages/session-projection/session-projection), [`session-projection-cache`](../packages/session-projection/session-projection-cache), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`tool-web`](../packages/web/tool-web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`web`](../packages/web/web) | | [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`tools`](../packages/core/tools) | -| [`timeout-policy`](../packages/timeout/timeout-policy) | `timeout` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | -| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`tools`](../packages/core/tools) | -| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | -| [`tool-cordis`](../packages/cordis/tool-cordis) | `cordis` | [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | -| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | -| [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy) | `session-persistence` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | -| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query) | +| [`tool-todo`](../packages/todo/tool-todo) | `todo` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) | +| [`plan-mode`](../packages/plan/plan-mode) | `plan` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-interaction`](../packages/interaction/user-interaction) | +| [`hooks-codex`](../packages/hooks/hooks-codex) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | +| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) | | [`tool-session-query`](../packages/session-query/tool-session-query) | `session-query` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | -| [`session-title-all-messages-llm`](../packages/session-title/session-title-all-messages-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`session-title-llm`](../packages/session-title/session-title-llm) | -| [`session-title-first-message-llm`](../packages/session-title/session-title-first-message-llm) | `session-title` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`session-title-llm`](../packages/session-title/session-title-llm) | | [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | -| [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | | [`client-ui-command`](../packages/client/ui-command) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | | [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | | [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | @@ -1338,37 +1326,43 @@ flowchart TD | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | | [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) | +| [`timeout-policy`](../packages/guard/timeout-policy) | `guard` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | +| [`tool-ask-user`](../packages/interaction/tool-ask-user) | `interaction` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/interaction/user-interaction) | | [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) | | [`tool-bash-persistent`](../packages/pty/tool-bash-persistent) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) | | [`tool-pty`](../packages/pty/tool-pty) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`pty`](../packages/pty/pty), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | +| [`tool-cordis`](../packages/self-modification/tool-cordis) | `self-modification` | [`invariants`](../packages/support/invariants), [`scope`](../packages/core/scope), [`tools`](../packages/core/tools) | +| [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) | +| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) | +| [`session-title-all-messages-llm`](../packages/session/session-title-all-messages-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | +| [`session-title-first-message-llm`](../packages/session/session-title-first-message-llm) | `session` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) | | [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | -| [`session-telemetry-otel`](../packages/telemetry/session-telemetry-otel) | `telemetry` | [`brand`](../packages/util/brand), [`command-feedback`](../packages/feedback/command-feedback), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`session-telemetry`](../packages/telemetry/session-telemetry) | | [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | +| [`tool-bash`](../packages/bash/tool-bash) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-pwsh`](../packages/bash/tool-pwsh) | `bash` | [`agent`](../packages/core/agent), [`bash`](../packages/bash/bash), [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-claude-code`](../packages/subagent/subagent-claude-code) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | -| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval) | +| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) | | [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) | | [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`tool-subagent-report`](../packages/subagent/tool-subagent-report) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | -| [`repository-plugin`](../packages/cordis/repository-plugin) | `cordis` | [`invariants`](../packages/support/invariants), [`mcp-client`](../packages/mcp/mcp-client), [`paths`](../packages/util/paths), [`skill-local`](../packages/skill/skill-local) | -| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | +| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`web-app`](../packages/bundle/web-app) | `bundle` | [`bash-env`](../packages/bash/bash-env), [`invariants`](../packages/support/invariants), [`system-prompt`](../packages/core/system-prompt) | | [`client-ui-model`](../packages/client/ui-model) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-command`](../packages/client/ui-command), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | -| [`client-ui-permission`](../packages/client/ui-permission) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-command`](../packages/client/ui-command), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`permission`](../packages/ui/permission) | +| [`client-ui-permission`](../packages/client/ui-permission) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-schema-form`](../packages/client/schema-form), [`client-ui-command`](../packages/client/ui-command), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`permission`](../packages/interaction/permission) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`plan-mode`](../packages/plan/plan-mode) | | [`client-ui-skill`](../packages/client/ui-skill) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-tool`](../packages/client/ui-tool), [`invariants`](../packages/support/invariants) | | [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | -| [`sdk-protocol`](../packages/sdk/sdk-protocol) | `sdk` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`sdk-protocol`](../packages/scaffold/protocol) | `scaffold` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`repository-plugin`](../packages/self-modification/repository-plugin) | `self-modification` | [`invariants`](../packages/support/invariants), [`mcp-client`](../packages/mcp/mcp-client), [`paths`](../packages/util/paths), [`skill-local`](../packages/skill/skill-local) | | [`tool-ralph`](../packages/workflow/tool-ralph) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`workflow-workerthread`](../packages/workflow/workflow-workerthread) | `workflow` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | -| [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/sdk-protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | +| [`subagent-codex`](../packages/subagent/subagent-codex) | `subagent` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) | | [`subagent-fork`](../packages/subagent/subagent-fork) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | | [`subagent-spawn`](../packages/subagent/subagent-spawn) | `subagent` | [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | -| [`jsonrpc`](../packages/ui/jsonrpc) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/sdk/sdk-protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | -| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`bash-env`](../packages/bash/bash-env), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks-local`](../packages/tasks/tasks-local), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | -| [`sdk-client`](../packages/sdk/sdk-client) | `sdk` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/sdk-protocol), [`session`](../packages/core/session) | -| [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/sdk-client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | -| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/ui/app-boot), [`invariants`](../packages/support/invariants), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | +| [`agent-spine-demo`](../packages/examples/agent-spine-demo) | `examples` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`bash-env`](../packages/bash/bash-env), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`paths`](../packages/util/paths), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`skill-local`](../packages/skill/skill-local), [`system-prompt`](../packages/core/system-prompt), [`tasks-local`](../packages/tasks/tasks-local), [`tool-bash`](../packages/bash/tool-bash), [`tool-goal`](../packages/goal/tool-goal), [`tool-skill`](../packages/skill/tool-skill), [`tool-tasks`](../packages/tasks/tool-tasks), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | +| [`jsonrpc`](../packages/scaffold/server) | `scaffold` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`llm-deepseek`](../packages/llm/llm-deepseek), [`scope`](../packages/core/scope), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent) | +| [`sdk-client`](../packages/scaffold/client) | `scaffold` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/scaffold/protocol), [`session`](../packages/core/session) | +| [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/scaffold/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | +| [`acp-demo`](../packages/examples/acp-demo) | `examples` | [`acp`](../packages/acp/acp), [`agent-spine-demo`](../packages/examples/agent-spine-demo), [`app-boot`](../packages/boot/app-boot), [`invariants`](../packages/support/invariants), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`tools`](../packages/core/tools), [`workspace-context`](../packages/context/workspace-context) | diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index daf2ea7ab2..b238e56399 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -125,7 +125,7 @@ Source: [`packages/core/agent/src/types.ts:300`](../packages/core/agent/src/type Types: [CallId](core-data-structures/core.md) -Source: [`packages/ui/user-approval/src/index.ts:44`](../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts) #### `approval/decided` — log-only @@ -141,7 +141,7 @@ Source: [`packages/ui/user-approval/src/index.ts:44`](../packages/ui/user-approv } ``` -Source: [`packages/ui/user-approval/src/index.ts:55`](../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:55`](../packages/interaction/user-approval/src/index.ts) #### `approval/policy` — log-only @@ -161,7 +161,7 @@ Source: [`packages/ui/user-approval/src/index.ts:55`](../packages/ui/user-approv } ``` -Source: [`packages/ui/user-approval/src/index.ts:67`](../packages/ui/user-approval/src/index.ts) +Source: [`packages/interaction/user-approval/src/index.ts:67`](../packages/interaction/user-approval/src/index.ts) ### `assistant/*` @@ -211,7 +211,7 @@ Source: [`packages/core/session/src/types.ts:245`](../packages/core/session/src/ } ``` -Source: [`packages/ui/commands/src/index.ts:151`](../packages/ui/commands/src/index.ts) +Source: [`packages/interaction/commands/src/index.ts:151`](../packages/interaction/commands/src/index.ts) #### `command/run` — log-only @@ -229,7 +229,7 @@ Source: [`packages/ui/commands/src/index.ts:151`](../packages/ui/commands/src/in 'command/run': { commandId: CommandId; name: string; args?: string; source: CommandSource } ``` -Source: [`packages/ui/commands/src/index.ts:144`](../packages/ui/commands/src/index.ts) +Source: [`packages/interaction/commands/src/index.ts:144`](../packages/interaction/commands/src/index.ts) ### `compact/*` @@ -449,7 +449,7 @@ Source: [`packages/llm/llm-retry/src/index.ts:17`](../packages/llm/llm-retry/src 'permission/preset': { preset: string } ``` -Source: [`packages/ui/permission/src/index.ts:50`](../packages/ui/permission/src/index.ts) +Source: [`packages/interaction/permission/src/index.ts:50`](../packages/interaction/permission/src/index.ts) ### `plan/*` diff --git a/examples/web-cordis/README.i18n.yaml b/examples/web-cordis/README.i18n.yaml index 0564a4e0fc..86cc276a4e 100644 --- a/examples/web-cordis/README.i18n.yaml +++ b/examples/web-cordis/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write examples/web-cordis/README.md -README.md: 75c7610fecc20777607c7c8cbb6ed2fc7f590b73 -README.zh.md: 5c25347ee17037bbfc7d10a5507424ce52e3faff +README.md: 21fe0a210b2e591a96dc254014a0f91ed9afa2ba +README.zh.md: 35158affd5cbdd6f8fa7f5910500f21c2b241309 diff --git a/examples/web-cordis/README.md b/examples/web-cordis/README.md index 75c7610fec..21fe0a210b 100644 --- a/examples/web-cordis/README.md +++ b/examples/web-cordis/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Self-referential demonstration of [`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md). The agent can inspect its current Cordis process and mount or unmount model-authored plugins in memory. Temporary plugins disappear when they are unmounted or the process exits and may affect other sessions in the same process. +Self-referential demonstration of [`@deepseek-ai/dsh-tool-cordis`](../../packages/self-modification/tool-cordis/README.md). The agent can inspect its current Cordis process and mount or unmount model-authored plugins in memory. Temporary plugins disappear when they are unmounted or the process exits and may affect other sessions in the same process. ## Run it @@ -18,4 +18,4 @@ Start the ACP automation server instead: pnpm run demo:cordis acp ``` -Both commands require `DEEPSEEK_API_KEY`. The [Cordis tool reference](../../packages/cordis/tool-cordis/README.md) owns the tool, lifecycle, and safety contracts. +Both commands require `DEEPSEEK_API_KEY`. The [Cordis tool reference](../../packages/self-modification/tool-cordis/README.md) owns the tool, lifecycle, and safety contracts. diff --git a/examples/web-cordis/README.zh.md b/examples/web-cordis/README.zh.md index 5c25347ee1..35158affd5 100644 --- a/examples/web-cordis/README.zh.md +++ b/examples/web-cordis/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -[`@deepseek-ai/dsh-tool-cordis`](../../packages/cordis/tool-cordis/README.md) 的自指示例。agent(智能体)可以检查当前 Cordis 进程,并在内存中挂载或卸载模型编写的插件。临时插件会在卸载或进程退出时消失,并可能影响同一进程中的其他会话。 +[`@deepseek-ai/dsh-tool-cordis`](../../packages/self-modification/tool-cordis/README.md) 的自指示例。agent(智能体)可以检查当前 Cordis 进程,并在内存中挂载或卸载模型编写的插件。临时插件会在卸载或进程退出时消失,并可能影响同一进程中的其他会话。 ## 运行 @@ -18,4 +18,4 @@ pnpm run demo:cordis pnpm run demo:cordis acp ``` -这两条命令都需要 `DEEPSEEK_API_KEY`。工具、生命周期和安全契约由 [Cordis 工具参考](../../packages/cordis/tool-cordis/README.md)定义。 +这两条命令都需要 `DEEPSEEK_API_KEY`。工具、生命周期和安全契约由 [Cordis 工具参考](../../packages/self-modification/tool-cordis/README.md)定义。 diff --git a/packages/api/remotes/tsconfig.host.json b/packages/api/remotes/tsconfig.host.json index 1d4c35a9e2..1082a0d94b 100644 --- a/packages/api/remotes/tsconfig.host.json +++ b/packages/api/remotes/tsconfig.host.json @@ -21,7 +21,7 @@ "path": "../../core/session" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/bash/bash-env/tsconfig.json b/packages/bash/bash-env/tsconfig.json index bcf5eb5229..d48ddfc73b 100644 --- a/packages/bash/bash-env/tsconfig.json +++ b/packages/bash/bash-env/tsconfig.json @@ -27,7 +27,7 @@ "path": "../../core/tools" }, { - "path": "../../session-persistence/session-persistence" + "path": "../../session/session-persistence" }, { "path": "../../support/invariants" diff --git a/packages/bundle/README.i18n.yaml b/packages/bundle/README.i18n.yaml index 5199e0939e..98df59d988 100644 --- a/packages/bundle/README.i18n.yaml +++ b/packages/bundle/README.i18n.yaml @@ -1,12 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # 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: -<<<<<<<< HEAD:packages/bundle/README.i18n.yaml # pnpm run verify-translation-pairing --write packages/bundle/README.md -README.md: 4759170435a80e85731446cef21d24fff2abed66 -README.zh.md: 1ef610a1b7b3c591c9a900e04f2d8096b0b086b9 -======== -# pnpm run verify-translation-pairing --write packages/interaction/tui/README.md -README.md: c81cac891403e5294c4456ce4d4048ecd74666ce -README.zh.md: 01055619f4df460284564f0a1816de366d809e01 ->>>>>>>> a58746f4cc (refactor(packages): dissolve ui/ and rename sdk/ to scaffold/):packages/interaction/tui/README.i18n.yaml +README.md: af06fe921a66378894a4ab0174ef403499f5f68c +README.zh.md: ef2027e347b79e14574ba1164aa9fddd364327e9 diff --git a/packages/bundle/README.md b/packages/bundle/README.md index 4759170435..af06fe921a 100644 --- a/packages/bundle/README.md +++ b/packages/bundle/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Profile bundles: npm packages whose manifest declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`, making them installable patch layers for `dsh --profile` compositions ([profile contract](../ui/app-boot/README.md#profiles)). A bundle's substance is its patch list; some also ship runtime glue plugins their patch mounts. +Profile bundles: npm packages whose manifest declares `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`, making them installable patch layers for `dsh --profile` compositions ([profile contract](../boot/app-boot/README.md#profiles)). A bundle's substance is its patch list; some also ship runtime glue plugins their patch mounts. | Package | Role | ctx key | |---|---|---| diff --git a/packages/bundle/README.zh.md b/packages/bundle/README.zh.md index 1ef610a1b7..ef2027e347 100644 --- a/packages/bundle/README.zh.md +++ b/packages/bundle/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -Profile 组合包:在 manifest(元数据清单)中声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 的 npm 包,因此可作为 patch 层安装进 `dsh --profile` 组合([profile 契约](../ui/app-boot/README.md#profiles))。组合包的实体是它的 patch 列表;有些组合包还附带由其 patch 挂载的运行时粘合插件。 +Profile 组合包:在 manifest(元数据清单)中声明 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }` 的 npm 包,因此可作为 patch 层安装进 `dsh --profile` 组合([profile 契约](../boot/app-boot/README.md#profiles))。组合包的实体是它的 patch 列表;有些组合包还附带由其 patch 挂载的运行时粘合插件。 | 包 | 职责 | ctx key | |---|---|---| diff --git a/packages/compact/command-compact/README.i18n.yaml b/packages/compact/command-compact/README.i18n.yaml index 35ebe66e2b..68dfebd1b9 100644 --- a/packages/compact/command-compact/README.i18n.yaml +++ b/packages/compact/command-compact/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/compact/command-compact/README.md -README.md: 54f341e39447a423964b7d7435cfb638857eda6e -README.zh.md: d4a122b8a19cdf907212ad019b2528ae52d03886 +README.md: 5af39e82928d64c11474199b6b30058e9d289f45 +README.zh.md: a582bd5272b2e39ac1ffbf839888a4006b6d44bc diff --git a/packages/compact/command-compact/README.md b/packages/compact/command-compact/README.md index 54f341e394..5af39e8292 100644 --- a/packages/compact/command-compact/README.md +++ b/packages/compact/command-compact/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Human-facing `/compact` control over [`ctx.compact`](../compact/README.md). The plugin registers one global command through [`ctx.commands`](../../ui/commands/README.md), so every composed command adapter discovers and executes it without a model turn. The [queued manual compaction Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md) owns the admission, lock, and durability decisions. +Human-facing `/compact` control over [`ctx.compact`](../compact/README.md). The plugin registers one global command through [`ctx.commands`](../../interaction/commands/README.md), so every composed command adapter discovers and executes it without a model turn. The [queued manual compaction Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md) owns the admission, lock, and durability decisions. ## Command contract diff --git a/packages/compact/command-compact/README.zh.md b/packages/compact/command-compact/README.zh.md index d4a122b8a1..a582bd5272 100644 --- a/packages/compact/command-compact/README.zh.md +++ b/packages/compact/command-compact/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -通过 [`ctx.compact`](../compact/README.md) 提供面向用户的 `/compact` 压缩(compaction)控制。该插件通过 [`ctx.commands`](../../ui/commands/README.md) 注册一个全局命令,因此组合中的每个命令适配器都能发现并执行它,无需模型轮次。[排队手动压缩 Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md)拥有接纳、锁与持久性决策。 +通过 [`ctx.compact`](../compact/README.md) 提供面向用户的 `/compact` 压缩(compaction)控制。该插件通过 [`ctx.commands`](../../interaction/commands/README.md) 注册一个全局命令,因此组合中的每个命令适配器都能发现并执行它,无需模型轮次。[排队手动压缩 Agent Note](../../../.agents/notes/implemented/feature/2026-07-30-queued-manual-compaction.md)拥有接纳、锁与持久性决策。 ## 命令契约 diff --git a/packages/credentials/credentials-local/README.i18n.yaml b/packages/credentials/credentials-local/README.i18n.yaml index 07a3efd5c3..182ac31809 100644 --- a/packages/credentials/credentials-local/README.i18n.yaml +++ b/packages/credentials/credentials-local/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/credentials/credentials-local/README.md -README.md: 8e95a890a8e38172cf8984653a01c59570f0061a -README.zh.md: 04ad07ae4e703ab0416d1d8f1bb6a6ff90adf337 +README.md: 6051c07628c0214f9396d12554719c5614610826 +README.zh.md: c6f3901af925d4bd31d2dcfbf5f5f65de62dac81 diff --git a/packages/credentials/credentials-local/README.md b/packages/credentials/credentials-local/README.md index 8e95a890a8..6051c07628 100644 --- a/packages/credentials/credentials-local/README.md +++ b/packages/credentials/credentials-local/README.md @@ -51,7 +51,7 @@ External edits publish `credentials/updated` per changed reference after the sna ## Security boundary -The document is `0600` under a `0700` directory, which stops other OS users — **not** the model. Tool processes (bash, the filesystem tools) run as the same user, and the shipped `workspace-write` file policy confines mutations rather than reads, so they can read this file exactly like any other file the user owns; no sandbox mode singles it out. What the harness does hold to is narrower: it never hands the model a resolved path to the document, and never loads it into the process environment — unlike `$DSH_HOME/.env`, which is the user's ordinary environment layer (see [app-boot's Harness-home layers](../../ui/app-boot/README.md#profiles)) — so reaching the value takes a deliberate read of a path the agent was not given. +The document is `0600` under a `0700` directory, which stops other OS users — **not** the model. Tool processes (bash, the filesystem tools) run as the same user, and the shipped `workspace-write` file policy confines mutations rather than reads, so they can read this file exactly like any other file the user owns; no sandbox mode singles it out. What the harness does hold to is narrower: it never hands the model a resolved path to the document, and never loads it into the process environment — unlike `$DSH_HOME/.env`, which is the user's ordinary environment layer (see [app-boot's Harness-home layers](../../boot/app-boot/README.md#profiles)) — so reaching the value takes a deliberate read of a path the agent was not given. That is discretion, not a boundary. A deployment that must keep provider keys away from its own agent cannot get there with file permissions; an OS-keychain provider — a store the model's processes cannot read at all — is the deferred answer and belongs beside this provider as a sibling package. diff --git a/packages/credentials/credentials-local/README.zh.md b/packages/credentials/credentials-local/README.zh.md index 04ad07ae4e..c6f3901af9 100644 --- a/packages/credentials/credentials-local/README.zh.md +++ b/packages/credentials/credentials-local/README.zh.md @@ -51,7 +51,7 @@ OPENAI_API_KEY: sk-… ## 安全边界 -文档在 `0700` 目录下以 `0600` 权限存放,这挡得住其他 OS 用户,**挡不住**模型。工具进程(bash、文件系统工具)以同一用户身份运行,而已交付的 `workspace-write` 文件策略限制的是修改而非读取,因此它们读这个文件与读该用户拥有的任何其他文件毫无二致;也没有任何沙箱模式会把它单独挑出来。harness 真正守住的更窄:它绝不把该文档的解析后路径交给模型,也绝不把它载入进程环境——这与用户的普通环境层 `$DSH_HOME/.env` 不同(见 [app-boot 的 Harness home 各层](../../ui/app-boot/README.md#profiles))——因此要拿到这个值,需要刻意去读一条并未交给 agent(智能体)的路径。 +文档在 `0700` 目录下以 `0600` 权限存放,这挡得住其他 OS 用户,**挡不住**模型。工具进程(bash、文件系统工具)以同一用户身份运行,而已交付的 `workspace-write` 文件策略限制的是修改而非读取,因此它们读这个文件与读该用户拥有的任何其他文件毫无二致;也没有任何沙箱模式会把它单独挑出来。harness 真正守住的更窄:它绝不把该文档的解析后路径交给模型,也绝不把它载入进程环境——这与用户的普通环境层 `$DSH_HOME/.env` 不同(见 [app-boot 的 Harness home 各层](../../boot/app-boot/README.md#profiles))——因此要拿到这个值,需要刻意去读一条并未交给 agent(智能体)的路径。 这是审慎,不是边界。必须让提供方密钥远离自身 agent 的部署无法靠文件权限做到;OS 钥匙串提供方——一个模型的进程根本读不到的存储——才是延后的答案,它应当作为平级包与本提供方并列。 diff --git a/packages/examples/jsonrpc-demo/README.i18n.yaml b/packages/examples/jsonrpc-demo/README.i18n.yaml index 4e7935dd3a..c47938ce3b 100644 --- a/packages/examples/jsonrpc-demo/README.i18n.yaml +++ b/packages/examples/jsonrpc-demo/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/examples/jsonrpc-demo/README.md -README.md: 2cc496da3e22baf9e87afe4c3ca183ae3f16a5ae -README.zh.md: 98d397b3c346ee47e9a667b2ccec0179f009a05b +README.md: fff8e78698cd3d6320606084ef5c533be7c52633 +README.zh.md: 75382b97ea1837cf1415e8a7f5004206596e168c diff --git a/packages/examples/jsonrpc-demo/README.md b/packages/examples/jsonrpc-demo/README.md index 2cc496da3e..fff8e78698 100644 --- a/packages/examples/jsonrpc-demo/README.md +++ b/packages/examples/jsonrpc-demo/README.md @@ -2,11 +2,11 @@ English | [中文](README.zh.md) -Bin-only app that boots an external `cordis.yml`; its [`jsonrpc`](../../ui/jsonrpc/README.md) entry serves SDK clients over newline-delimited stdio. The config composes the spine, backends, and serving plugin. The published bin is `dsh-jsonrpc-agent`, and `lib/bin.js` also ships as the `dsh-jsonrpc-agent-pkg` [single-executable runtime](../../../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md) used by the Python SDK. +Bin-only app that boots an external `cordis.yml`; its [`jsonrpc`](../../scaffold/server/README.md) entry serves SDK clients over newline-delimited stdio. The config composes the spine, backends, and serving plugin. The published bin is `dsh-jsonrpc-agent`, and `lib/bin.js` also ships as the `dsh-jsonrpc-agent-pkg` [single-executable runtime](../../../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md) used by the Python SDK. ## Config discovery -The first non-empty channel wins: `$DSH_CORDIS_CONFIG`, then positional `argv[2]`. If neither names an existing file, the bin prints one-line usage to stderr and exits 1; there is no working-directory or built-in fallback. [`dsh-app-boot`](../../ui/app-boot/README.md) makes plugin load failures fatal. This protocol does not use `DSH_SNAPSHOT`. +The first non-empty channel wins: `$DSH_CORDIS_CONFIG`, then positional `argv[2]`. If neither names an existing file, the bin prints one-line usage to stderr and exits 1; there is no working-directory or built-in fallback. [`dsh-app-boot`](../../boot/app-boot/README.md) makes plugin load failures fatal. This protocol does not use `DSH_SNAPSHOT`. A config without `dsh-jsonrpc` is valid and serves nothing; the bin does not designate a server plugin. diff --git a/packages/examples/jsonrpc-demo/README.zh.md b/packages/examples/jsonrpc-demo/README.zh.md index 98d397b3c3..75382b97ea 100644 --- a/packages/examples/jsonrpc-demo/README.zh.md +++ b/packages/examples/jsonrpc-demo/README.zh.md @@ -2,11 +2,11 @@ [English](README.md) | 中文 -只包含 bin 的应用,启动外部 `cordis.yml`;其 [`jsonrpc`](../../ui/jsonrpc/README.md) 入口通过按换行分隔的 stdio 为 SDK 客户端提供服务。配置负责组合主干、后端和服务插件。发布的 bin 名为 `dsh-jsonrpc-agent`,`lib/bin.js` 还会作为 Python SDK 使用的 `dsh-jsonrpc-agent-pkg` [单文件可执行运行时](../../../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md)交付。 +只包含 bin 的应用,启动外部 `cordis.yml`;其 [`jsonrpc`](../../scaffold/server/README.md) 入口通过按换行分隔的 stdio 为 SDK 客户端提供服务。配置负责组合主干、后端和服务插件。发布的 bin 名为 `dsh-jsonrpc-agent`,`lib/bin.js` 还会作为 Python SDK 使用的 `dsh-jsonrpc-agent-pkg` [单文件可执行运行时](../../../.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md)交付。 ## 配置发现 -第一个非空通道生效:先 `$DSH_CORDIS_CONFIG`,再位置参数 `argv[2]`。如果二者都没有指向现有文件,bin 会向 stderr 打印单行用法并以 1 退出;没有工作目录回退或内置回退。[`dsh-app-boot`](../../ui/app-boot/README.md) 会使插件加载失败成为致命错误。此协议不使用 `DSH_SNAPSHOT`。 +第一个非空通道生效:先 `$DSH_CORDIS_CONFIG`,再位置参数 `argv[2]`。如果二者都没有指向现有文件,bin 会向 stderr 打印单行用法并以 1 退出;没有工作目录回退或内置回退。[`dsh-app-boot`](../../boot/app-boot/README.md) 会使插件加载失败成为致命错误。此协议不使用 `DSH_SNAPSHOT`。 不含 `dsh-jsonrpc` 的配置仍然有效,只是不提供任何服务;bin 不会指定服务器插件。 diff --git a/packages/feedback/README.i18n.yaml b/packages/feedback/README.i18n.yaml index 4ad5a93fb5..d0fe492135 100644 --- a/packages/feedback/README.i18n.yaml +++ b/packages/feedback/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/feedback/README.md -README.md: d2a4a5a27e1c661d2f62b328578fd890a0c622ee -README.zh.md: 2fa42e3bb5f05dfc425356f302f44e497b100f24 +README.md: af8e9d5c4903594299284d09f880aa8929f5e051 +README.zh.md: 01a26798d0b7dc8b376929252a60a99fff11b640 diff --git a/packages/feedback/README.md b/packages/feedback/README.md index d2a4a5a27e..af8e9d5c49 100644 --- a/packages/feedback/README.md +++ b/packages/feedback/README.md @@ -8,4 +8,4 @@ The feedback family lets a human record a remark about the session without actin |---|---|---| | `command-feedback/` | Trigger-independent `feedback/record` event plus the human-facing `/feedback` producer | — | -A recorded remark is log-only: it never enters the model surface or derived history. When mounted, [`dsh-session-telemetry-otel`](../telemetry/session-telemetry-otel/) observes `feedback/record` to release a pending telemetry prefix or warn that disabled telemetry leaves the feedback local; capture itself remains independent of that policy. +A recorded remark is log-only: it never enters the model surface or derived history. When mounted, [`dsh-session-telemetry-otel`](../session/session-telemetry-otel) observes `feedback/record` to release a pending telemetry prefix or warn that disabled telemetry leaves the feedback local; capture itself remains independent of that policy. diff --git a/packages/feedback/README.zh.md b/packages/feedback/README.zh.md index 2fa42e3bb5..01a26798d0 100644 --- a/packages/feedback/README.zh.md +++ b/packages/feedback/README.zh.md @@ -8,4 +8,4 @@ feedback 家族让人类记录对会话的评价,但不据此采取任何动 |---|---|---| | `command-feedback/` | 与触发方式无关的 `feedback/record` 事件,以及面向用户的 `/feedback` 生产方 | 无 | -被记录的评价仅写入日志:它绝不会进入模型 surface 或派生历史。挂载后,[`dsh-session-telemetry-otel`](../telemetry/session-telemetry-otel/) 会观察 `feedback/record`,以释放待处理的遥测前缀,或在遥测已禁用时警告反馈将留在本地;采集本身与该策略相互独立。 +被记录的评价仅写入日志:它绝不会进入模型 surface 或派生历史。挂载后,[`dsh-session-telemetry-otel`](../session/session-telemetry-otel) 会观察 `feedback/record`,以释放待处理的遥测前缀,或在遥测已禁用时警告反馈将留在本地;采集本身与该策略相互独立。 diff --git a/packages/feedback/command-feedback/README.i18n.yaml b/packages/feedback/command-feedback/README.i18n.yaml index ad645a61d3..20eb2741d6 100644 --- a/packages/feedback/command-feedback/README.i18n.yaml +++ b/packages/feedback/command-feedback/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/feedback/command-feedback/README.md -README.md: 1923267eb3a25a4be564fa4f4535f7a3459ca481 -README.zh.md: 674665167030aa6214e9acaa7e6f5314a78c5b14 +README.md: e2eb6d4cf2b40e83efad1fa158edd72578658f56 +README.zh.md: f87067d67cf9dcb8f4da97afe3a9d59eb828faf7 diff --git a/packages/feedback/command-feedback/README.md b/packages/feedback/command-feedback/README.md index 1923267eb3..e2eb6d4cf2 100644 --- a/packages/feedback/command-feedback/README.md +++ b/packages/feedback/command-feedback/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Trigger-independent session feedback plus human-facing `/feedback` capture. The package exports `recordFeedback(session, text)`, which appends one log-only `feedback/record` event. Its plugin registers one global command through [`ctx.commands`](../../ui/commands/README.md), so every composed command adapter discovers it; the shipped Web client executes it without a model turn. +Trigger-independent session feedback plus human-facing `/feedback` capture. The package exports `recordFeedback(session, text)`, which appends one log-only `feedback/record` event. Its plugin registers one global command through [`ctx.commands`](../../interaction/commands/README.md), so every composed command adapter discovers it; the shipped Web client executes it without a model turn. ## Command contract @@ -15,9 +15,9 @@ Surrounding whitespace is discarded, but feedback is otherwise unparsed: no trun ## What this plugin does and does not do -`recordFeedback(session, text)` is the command-independent write path. It rejects empty normalized text and appends `feedback/record { text }`; a different UI, hook, or host integration can call it without constructing a slash command. The `/feedback` handler uses that producer and starts no model work. The optional [`dsh-session-telemetry-otel`](../../telemetry/session-telemetry-otel/) consumer observes the event without changing its capture contract. +`recordFeedback(session, text)` is the command-independent write path. It rejects empty normalized text and appends `feedback/record { text }`; a different UI, hook, or host integration can call it without constructing a slash command. The `/feedback` handler uses that producer and starts no model work. The optional [`dsh-session-telemetry-otel`](../../session/session-telemetry-otel) consumer observes the event without changing its capture contract. -The feedback text appears in exactly one durable payload: `feedback/record`. [`dsh-commands`](../../ui/commands/README.md) still appends its generic `command/run` / `command/done` pairing, but this definition sets `recordInput: false`, so `command/run` omits `args`; the paired `command/done` carries only the outcome. All three events are log-only and absent from the ordered surface, `deriveMessages()`, and model requests. These appends start persistence's ordinary eager drain, but neither producer forces `session/flush`, so acknowledgement means the feedback is in the log, not that it has reached disk. Rejected empty input leaves only the command pairing settled as `kind: 'error'`, with no `feedback/record`. +The feedback text appears in exactly one durable payload: `feedback/record`. [`dsh-commands`](../../interaction/commands/README.md) still appends its generic `command/run` / `command/done` pairing, but this definition sets `recordInput: false`, so `command/run` omits `args`; the paired `command/done` carries only the outcome. All three events are log-only and absent from the ordered surface, `deriveMessages()`, and model requests. These appends start persistence's ordinary eager drain, but neither producer forces `session/flush`, so acknowledgement means the feedback is in the log, not that it has reached disk. Rejected empty input leaves only the command pairing settled as `kind: 'error'`, with no `feedback/record`. The event is authoritative rather than the command record because feedback may arrive through a trigger other than `/feedback`. Keeping the payload out of `command/run` avoids two records carrying the same text. diff --git a/packages/feedback/command-feedback/README.zh.md b/packages/feedback/command-feedback/README.zh.md index 6746651670..f87067d67c 100644 --- a/packages/feedback/command-feedback/README.zh.md +++ b/packages/feedback/command-feedback/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -与触发方式无关的会话反馈,以及面向用户的 `/feedback` 采集。本包导出 `recordFeedback(session, text)`,后者追加一个仅写入日志的 `feedback/record` 事件。该插件通过 [`ctx.commands`](../../ui/commands/README.md) 注册一个全局命令,因此每个已组合的命令适配器都能发现它;随附的 Web 客户端无需模型轮次即可执行。 +与触发方式无关的会话反馈,以及面向用户的 `/feedback` 采集。本包导出 `recordFeedback(session, text)`,后者追加一个仅写入日志的 `feedback/record` 事件。该插件通过 [`ctx.commands`](../../interaction/commands/README.md) 注册一个全局命令,因此每个已组合的命令适配器都能发现它;随附的 Web 客户端无需模型轮次即可执行。 ## 命令契约 @@ -15,9 +15,9 @@ ## 本插件做什么、不做什么 -`recordFeedback(session, text)` 是不依赖命令的写入路径。它拒绝规范化后为空的文本,并追加 `feedback/record { text }`;其他 UI、钩子或 host 集成无需构造斜杠命令即可调用它。`/feedback` 处理器通过该生产方写入,且不启动任何模型工作。可选的 [`dsh-session-telemetry-otel`](../../telemetry/session-telemetry-otel/) 消费方会观察该事件,但不改变它的采集契约。 +`recordFeedback(session, text)` 是不依赖命令的写入路径。它拒绝规范化后为空的文本,并追加 `feedback/record { text }`;其他 UI、钩子或 host 集成无需构造斜杠命令即可调用它。`/feedback` 处理器通过该生产方写入,且不启动任何模型工作。可选的 [`dsh-session-telemetry-otel`](../../session/session-telemetry-otel) 消费方会观察该事件,但不改变它的采集契约。 -反馈文本只出现在一个持久载荷中:`feedback/record`。[`dsh-commands`](../../ui/commands/README.md) 仍会追加通用的 `command/run` / `command/done` 配对,但此定义设置了 `recordInput: false`,因此 `command/run` 会省略 `args`;配对的 `command/done` 只携带结果。三个事件都仅写入日志,不出现在有序 surface、`deriveMessages()` 以及模型请求中。这些追加会启动持久化的常规即时排空,但两个生产方都不会强制 `session/flush`,因此确认文本表示反馈已进入日志,而不表示它已经落盘。被拒绝的空输入只会留下以 `kind: 'error'` 结算的命令配对,不会产生 `feedback/record`。 +反馈文本只出现在一个持久载荷中:`feedback/record`。[`dsh-commands`](../../interaction/commands/README.md) 仍会追加通用的 `command/run` / `command/done` 配对,但此定义设置了 `recordInput: false`,因此 `command/run` 会省略 `args`;配对的 `command/done` 只携带结果。三个事件都仅写入日志,不出现在有序 surface、`deriveMessages()` 以及模型请求中。这些追加会启动持久化的常规即时排空,但两个生产方都不会强制 `session/flush`,因此确认文本表示反馈已进入日志,而不表示它已经落盘。被拒绝的空输入只会留下以 `kind: 'error'` 结算的命令配对,不会产生 `feedback/record`。 权威记录是该事件,而不是命令记录,因为反馈可能来自 `/feedback` 之外的触发方式。让载荷不进入 `command/run`,可避免两条记录携带相同文本。 diff --git a/packages/feedback/command-feedback/tsconfig.json b/packages/feedback/command-feedback/tsconfig.json index 0a99f13f01..f59431af9a 100644 --- a/packages/feedback/command-feedback/tsconfig.json +++ b/packages/feedback/command-feedback/tsconfig.json @@ -15,7 +15,7 @@ "path": "../../../vendor/cordis" }, { - "path": "../../ui/commands" + "path": "../../interaction/commands" }, { "path": "../../core/session" diff --git a/packages/goal/command-goal/README.i18n.yaml b/packages/goal/command-goal/README.i18n.yaml index 6586a5eda8..8f238d7429 100644 --- a/packages/goal/command-goal/README.i18n.yaml +++ b/packages/goal/command-goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/command-goal/README.md -README.md: a02803b3ef7f93f0c4910ec2be662cc7836d9048 -README.zh.md: 9c6d0cc6e7309a138e17f1a2d5ad6c5285913394 +README.md: 1ac1b8a0e2cb360708b1759ab5eb9ec3de597821 +README.zh.md: cc5f7ba9bd3c5e50d3b7ce204d8d8c400e4f0955 diff --git a/packages/goal/command-goal/README.md b/packages/goal/command-goal/README.md index a02803b3ef..1ac1b8a0e2 100644 --- a/packages/goal/command-goal/README.md +++ b/packages/goal/command-goal/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Human-facing `/goal` control over [`ctx.goals`](../goal/README.md). The plugin registers one global command through [`ctx.commands`](../../ui/commands/README.md), so every composed command adapter discovers and executes it without a model turn. The [human goal-command Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-human-goal-command.md) owns the UX and composition decisions. +Human-facing `/goal` control over [`ctx.goals`](../goal/README.md). The plugin registers one global command through [`ctx.commands`](../../interaction/commands/README.md), so every composed command adapter discovers and executes it without a model turn. The [human goal-command Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-human-goal-command.md) owns the UX and composition decisions. ## Command contract diff --git a/packages/goal/command-goal/README.zh.md b/packages/goal/command-goal/README.zh.md index 9c6d0cc6e7..cc5f7ba9bd 100644 --- a/packages/goal/command-goal/README.zh.md +++ b/packages/goal/command-goal/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -面向用户的 `/goal` 控制,基于 [`ctx.goals`](../goal/README.md) 实现。该插件通过 [`ctx.commands`](../../ui/commands/README.md) 注册一个全局命令,因此每个已组合的命令适配器都能发现并执行它,无需模型轮次。[用户 goal 命令 Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-human-goal-command.md)负责用户体验与组合决策。 +面向用户的 `/goal` 控制,基于 [`ctx.goals`](../goal/README.md) 实现。该插件通过 [`ctx.commands`](../../interaction/commands/README.md) 注册一个全局命令,因此每个已组合的命令适配器都能发现并执行它,无需模型轮次。[用户 goal 命令 Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-human-goal-command.md)负责用户体验与组合决策。 ## 命令契约 diff --git a/packages/guard/timeout-policy/src/index.ts b/packages/guard/timeout-policy/src/index.ts index 1c18fa5e13..f554b1a93e 100644 --- a/packages/guard/timeout-policy/src/index.ts +++ b/packages/guard/timeout-policy/src/index.ts @@ -6,7 +6,7 @@ * FIXME: settle the intended `@deepseek-ai/dsh-timeout-guard` rename before the * first tagged release — suggestion only, aligning the name with its `guard/` * home; decide at resolution time - * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * ([regrouping Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)). * * @module @deepseek-ai/dsh-timeout-policy */ diff --git a/packages/interaction/permission/README.i18n.yaml b/packages/interaction/permission/README.i18n.yaml index 34533f9da4..c8e4b28311 100644 --- a/packages/interaction/permission/README.i18n.yaml +++ b/packages/interaction/permission/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/permission/README.md README.md: 4f7f560bb81eaad3b6b95b2742432fa252682d5a -README.zh.md: 79d0ce9c095d3426f3219f04d9cb7ec3b161a184 +README.zh.md: d45f89e243ce2d8f6bb08943fb7e776ced106b5a diff --git a/packages/interaction/user-interaction/README.i18n.yaml b/packages/interaction/user-interaction/README.i18n.yaml index feab1813a7..aafe594c7e 100644 --- a/packages/interaction/user-interaction/README.i18n.yaml +++ b/packages/interaction/user-interaction/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/user-interaction/README.md -README.md: d62e75d110b8be339c5f9449b0834320f695ac99 -README.zh.md: 55258e85e56df2375ed8f195fa0b3b731a9cb816 +README.md: cba015e782623b3a5adf018303823577a0b96774 +README.zh.md: a5f944850c5ac4e05da59e8478167eef91796610 diff --git a/packages/plan/plan-mode/README.i18n.yaml b/packages/plan/plan-mode/README.i18n.yaml index b24d4b1954..86afdb6310 100644 --- a/packages/plan/plan-mode/README.i18n.yaml +++ b/packages/plan/plan-mode/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/plan/plan-mode/README.md -README.md: f2a78fe98b6a85ce91d727e95ddf62e6f77546b7 -README.zh.md: e1b5f111fd8f761fdd8f25558463c0cc327a14be +README.md: 6c8ba23b76e83665d4f8dcb5ecb41689347f6423 +README.zh.md: b306d5562af21dd9a95bb68c9acfc3f40c52136f diff --git a/packages/plan/plan-mode/README.md b/packages/plan/plan-mode/README.md index f2a78fe98b..6c8ba23b76 100644 --- a/packages/plan/plan-mode/README.md +++ b/packages/plan/plan-mode/README.md @@ -22,7 +22,7 @@ The Web client consumes the plugin-owned `/plan` command; other front doors may ## Session projection -When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md)), this package registers the `plan` projection unit under an injected child. The unit folds two event kinds: a `command/run` record named `plan` with recorded `args` sets the wanted target (`off` → inactive, anything else → active), and `plan/mode` commits the logged state and clears it; every other event returns the same state reference. `view` derives `{ active, pending }`, where `pending` is true only while an outstanding selection differs from the logged state — a pure replay quantity, so host restarts, other tabs, and cold reads all recover it from the log alone (the `/plan` handler calls `set()` before any failing path, keeping the logged request and the run plane from forking). The key merges into `SessionProjectionMap` from `src/types.ts` (served to host consumers via `./types` and client aggregates via `./client`); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected. +When the composition mounts `ctx.sessionProjections` ([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md)), this package registers the `plan` projection unit under an injected child. The unit folds two event kinds: a `command/run` record named `plan` with recorded `args` sets the wanted target (`off` → inactive, anything else → active), and `plan/mode` commits the logged state and clears it; every other event returns the same state reference. `view` derives `{ active, pending }`, where `pending` is true only while an outstanding selection differs from the logged state — a pure replay quantity, so host restarts, other tabs, and cold reads all recover it from the log alone (the `/plan` handler calls `set()` before any failing path, keeping the logged request and the run plane from forking). The key merges into `SessionProjectionMap` from `src/types.ts` (served to host consumers via `./types` and client aggregates via `./client`); the framework drives the unit and carriers serve the value on the history tail page and the `session/projection` push frame. Compositions without the registry are unaffected. ## Configuration diff --git a/packages/plan/plan-mode/README.zh.md b/packages/plan/plan-mode/README.zh.md index e1b5f111fd..b306d5562a 100644 --- a/packages/plan/plan-mode/README.zh.md +++ b/packages/plan/plan-mode/README.zh.md @@ -22,7 +22,7 @@ Web 客户端使用该插件提供的 `/plan` 命令;其他入口可以直接 ## 会话投影 -当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session-projection/session-projection/README.md))时,本包会在一个注入的子插件中注册 `plan` 投影单元。该单元折叠两类事件:名为 `plan` 且携带已记录 `args` 的 `command/run` 记录会设置目标状态(`off` → 未激活,其余 → 激活),`plan/mode` 会提交已记录状态并清除该目标;其他任何事件都返回同一个状态引用。`view` 推导 `{ active, pending }`,其中 `pending` 仅在尚未落实的选择与已记录状态不同时为 true。该值完全由日志回放得出,因此 host 重启、其他标签页和冷读都能仅凭日志恢复它。`/plan` 处理器会在任何可能失败的路径之前调用 `set()`,避免已写入日志的请求与运行面分叉。key 由 `src/types.ts` 通过声明合并加入 `SessionProjectionMap`:host 消费方经 `./types` 获取,client 聚合经 `./client` 获取。框架负责驱动该单元,载体通过历史尾页和 `session/projection` 推送帧提供其值。未挂载注册表的组合不受影响。 +当组合挂载 `ctx.sessionProjections`([`@deepseek-ai/dsh-session-projection`](../../session/session-projection/README.md))时,本包会在一个注入的子插件中注册 `plan` 投影单元。该单元折叠两类事件:名为 `plan` 且携带已记录 `args` 的 `command/run` 记录会设置目标状态(`off` → 未激活,其余 → 激活),`plan/mode` 会提交已记录状态并清除该目标;其他任何事件都返回同一个状态引用。`view` 推导 `{ active, pending }`,其中 `pending` 仅在尚未落实的选择与已记录状态不同时为 true。该值完全由日志回放得出,因此 host 重启、其他标签页和冷读都能仅凭日志恢复它。`/plan` 处理器会在任何可能失败的路径之前调用 `set()`,避免已写入日志的请求与运行面分叉。key 由 `src/types.ts` 通过声明合并加入 `SessionProjectionMap`:host 消费方经 `./types` 获取,client 聚合经 `./client` 获取。框架负责驱动该单元,载体通过历史尾页和 `session/projection` 推送帧提供其值。未挂载注册表的组合不受影响。 ## 配置 diff --git a/packages/scaffold/README.i18n.yaml b/packages/scaffold/README.i18n.yaml index 1480a14b69..7ddc22328c 100644 --- a/packages/scaffold/README.i18n.yaml +++ b/packages/scaffold/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/README.md -README.md: b166a351a53afe075ecb794fe2bf6f0702a435ae -README.zh.md: 6e2a65ea554487f48563c16e1defa37ef1a5e043 +README.md: 19c2f28f48d1c2d8747b635c10d6781c29e02f15 +README.zh.md: ecad2592eb32a30444f9112571379939da6afeec diff --git a/packages/scaffold/README.md b/packages/scaffold/README.md index b166a351a5..19c2f28f48 100644 --- a/packages/scaffold/README.md +++ b/packages/scaffold/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -This group contains developer tooling for Harness projects and the client stack for driving a Harness runtime from another process. Folders are role-named without the legacy `sdk-` prefix; npm names converge on `dsh-sdk-*` through the FIXME-tracked renames in the [regrouping Agent Note](../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md). +This group contains developer tooling for Harness projects and the client stack for driving a Harness runtime from another process. Folders are role-named without the legacy `sdk-` prefix; npm names converge on `dsh-sdk-*` through the FIXME-tracked renames in the [regrouping Agent Note](../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md). | Package | Role | |---|---| diff --git a/packages/scaffold/README.zh.md b/packages/scaffold/README.zh.md index 6e2a65ea55..ecad2592eb 100644 --- a/packages/scaffold/README.zh.md +++ b/packages/scaffold/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -本组包含 Harness 项目的开发者工具,以及从另一进程驱动 Harness 运行时的客户端栈。目录按角色命名,不带遗留的 `sdk-` 前缀;npm 名则经由[重新分组 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)中 FIXME 跟踪的改名收敛为 `dsh-sdk-*`。 +本组包含 Harness 项目的开发者工具,以及从另一进程驱动 Harness 运行时的客户端栈。目录按角色命名,不带遗留的 `sdk-` 前缀;npm 名则经由[重新分组 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)中 FIXME 跟踪的改名收敛为 `dsh-sdk-*`。 | 包 | 职责 | |---|---| diff --git a/packages/scaffold/client/README.i18n.yaml b/packages/scaffold/client/README.i18n.yaml index 0a71141cd6..c08016649d 100644 --- a/packages/scaffold/client/README.i18n.yaml +++ b/packages/scaffold/client/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/client/README.md -README.md: f27ec256330254156c45b136c21308529ea99e3d -README.zh.md: bd23762500a4da469839da8ddb50455e3419b553 +README.md: b33457875f81d11d09bab2e5aa5ce730e233c78a +README.zh.md: 271f07ffb0f97abe005971962beb517acfdc05a4 diff --git a/packages/scaffold/client/README.md b/packages/scaffold/client/README.md index f27ec25633..b33457875f 100644 --- a/packages/scaffold/client/README.md +++ b/packages/scaffold/client/README.md @@ -44,6 +44,6 @@ None; this package neither assembles nor sends a provider request. ## Known Limitations and Deferred Work - **No bundled-runtime resolution** — callers name the runtime executable explicitly; packaged-executable discovery stays Python-side until a TypeScript distribution consumer exists. -- **No mid-turn cancel** — the wire has no prompt-cancel method; abandoning a turn means closing the runtime (see the protocol's [Known Limitations](../sdk-protocol/README.md)). +- **No mid-turn cancel** — the wire has no prompt-cancel method; abandoning a turn means closing the runtime (see the protocol's [Known Limitations](../protocol/README.md)). - **No per-prompt result or cancel** — low-level `prompt()` returns only an enqueue receipt; high-level `run()` owns receipt-to-idle collection, and abandoning it means closing the runtime. - **Client→server notifications and server→client requests are unimplemented** on both wire ends; the transport carries them for future approval flows. diff --git a/packages/scaffold/client/README.zh.md b/packages/scaffold/client/README.zh.md index bd23762500..271f07ffb0 100644 --- a/packages/scaffold/client/README.zh.md +++ b/packages/scaffold/client/README.zh.md @@ -44,6 +44,6 @@ console.log(result.finalResponse) ## 已知限制与暂缓事项 - **无捆绑运行时解析**——调用方显式指定运行时可执行文件;打包可执行文件的发现留在 Python 侧,直到出现 TypeScript 发行版消费方。 -- **无轮次中取消**——协议层没有提示词取消方法;放弃轮次意味着关闭运行时(见协议的 [已知限制](../sdk-protocol/README.md))。 +- **无轮次中取消**——协议层没有提示词取消方法;放弃轮次意味着关闭运行时(见协议的 [已知限制](../protocol/README.md))。 - **没有逐提示词结果或取消**——低层 `prompt()` 只返回入队回执;高层 `run()` 负责从回执收集到 idle,放弃该过程意味着关闭运行时。 - **客户端→服务端通知与服务端→客户端请求**在协议两端都未实现;传输层为未来审批流保留了承载能力。 diff --git a/packages/scaffold/helper/src/index.ts b/packages/scaffold/helper/src/index.ts index 8c8e0db4b9..db4468aae5 100644 --- a/packages/scaffold/helper/src/index.ts +++ b/packages/scaffold/helper/src/index.ts @@ -3,7 +3,7 @@ * * FIXME: rename to `@deepseek-ai/dsh-sdk-helper` before the first tagged release — * the current name is indefensibly generic as a published name - * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * ([regrouping Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)). * * @module @deepseek-ai/dsh-helper */ diff --git a/packages/scaffold/protocol/README.i18n.yaml b/packages/scaffold/protocol/README.i18n.yaml index b77c8fce63..719a135b37 100644 --- a/packages/scaffold/protocol/README.i18n.yaml +++ b/packages/scaffold/protocol/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/protocol/README.md -README.md: 35b62d2ffc43c54dc81f6a5ecfb1a0ef4294439d -README.zh.md: a02ba94e7d8fae19bff48d8fda363e23fc173850 +README.md: 88a48957d0d44cec9f776d31eab7d25bd353de5f +README.zh.md: c662d28ec6eea5fe4829030daf210a91fd70ff0f diff --git a/packages/scaffold/protocol/README.md b/packages/scaffold/protocol/README.md index b8420eb19a..88a48957d0 100644 --- a/packages/scaffold/protocol/README.md +++ b/packages/scaffold/protocol/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delimited JSON-RPC 2.0 transport class plus the named request, result, and notification types both wire ends speak. The package root enumerates the protocol consumer interface; source modules are not exported as deep imports. The server side is the [`dsh-jsonrpc`](../../scaffold/server/README.md) plugin; clients are [`dsh-sdk-client`](../client/README.md) (TypeScript) and the [Python SDK](../../../python/README.md) (which mirrors these shapes but does not import them). A pure library — no plugin, no Config, no registration. +The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delimited JSON-RPC 2.0 transport class plus the named request, result, and notification types both wire ends speak. The package root enumerates the protocol consumer interface; source modules are not exported as deep imports. The server side is the [`dsh-jsonrpc`](../server/README.md) plugin; clients are [`dsh-sdk-client`](../client/README.md) (TypeScript) and the [Python SDK](../../../python/README.md) (which mirrors these shapes but does not import them). A pure library — no plugin, no Config, no registration. ## Transport @@ -26,7 +26,7 @@ The shared wire protocol for the DeepSeek Harness SDK runtime: one newline-delim ## Model Experience -None, as this package defines the client-facing wire protocol; the model-visible surfaces belong to the runtime plugins composed behind the serving [`dsh-jsonrpc`](../../scaffold/server/README.md) entry. +None, as this package defines the client-facing wire protocol; the model-visible surfaces belong to the runtime plugins composed behind the serving [`dsh-jsonrpc`](../server/README.md) entry. #### KV Cache effect @@ -35,5 +35,5 @@ None; this package neither assembles nor sends a provider request. ## Known Limitations and Deferred Work - **No protocol-version negotiation** — the handshake carries only `serverInfo.version` (`0.0.1`, unvalidated by clients); pre-release stance, no compatibility promise. -- **No cancel or session-close methods** — a client abandons a turn by closing the runtime process; see the [`dsh-jsonrpc` README](../../scaffold/server/README.md). +- **No cancel or session-close methods** — a client abandons a turn by closing the runtime process; see the [`dsh-jsonrpc` README](../server/README.md). - **Server→client requests are dead capability** — the transport supports them, but the server never sends one; the Python SDK's responder surface exists for future approval flows. diff --git a/packages/scaffold/protocol/README.zh.md b/packages/scaffold/protocol/README.zh.md index bc459f8817..c662d28ec6 100644 --- a/packages/scaffold/protocol/README.zh.md +++ b/packages/scaffold/protocol/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -DeepSeek Harness SDK 运行时的共享协议格式(wire format):一个按换行分帧的 JSON-RPC 2.0 传输类,加上协议两端共同使用的具名请求、结果与通知类型。包根枚举协议消费方接口;源模块不支持深层导入。服务端是 [`dsh-jsonrpc`](../../ui/jsonrpc/README.md) 插件;客户端是 [`dsh-sdk-client`](../sdk-client/README.md)(TypeScript)与 [Python SDK](../../../python/README.md)(后者复现这些结构但不导入它们)。纯库——无插件、无 Config、无注册。 +DeepSeek Harness SDK 运行时的共享协议格式(wire format):一个按换行分帧的 JSON-RPC 2.0 传输类,加上协议两端共同使用的具名请求、结果与通知类型。包根枚举协议消费方接口;源模块不支持深层导入。服务端是 [`dsh-jsonrpc`](../server/README.md) 插件;客户端是 [`dsh-sdk-client`](../client/README.md)(TypeScript)与 [Python SDK](../../../python/README.md)(后者复现这些结构但不导入它们)。纯库——无插件、无 Config、无注册。 ## 传输 @@ -26,7 +26,7 @@ DeepSeek Harness SDK 运行时的共享协议格式(wire format):一个按 ## 模型体验 -无,因为此包定义面向客户端的协议格式;模型可见接口属于组合在对外服务入口 [`dsh-jsonrpc`](../../ui/jsonrpc/README.md) 后方的运行时插件。 +无,因为此包定义面向客户端的协议格式;模型可见接口属于组合在对外服务入口 [`dsh-jsonrpc`](../server/README.md) 后方的运行时插件。 #### KV Cache 影响 @@ -35,5 +35,5 @@ DeepSeek Harness SDK 运行时的共享协议格式(wire format):一个按 ## 已知限制与暂缓事项 - **无协议版本协商**——握手只携带 `serverInfo.version`(`0.0.1`,客户端不校验);处于预发布阶段,无兼容承诺。 -- **无取消与会话关闭方法**——客户端放弃轮次的方式是关闭运行时进程;见 [`dsh-jsonrpc` README](../../ui/jsonrpc/README.md)。 +- **无取消与会话关闭方法**——客户端放弃轮次的方式是关闭运行时进程;见 [`dsh-jsonrpc` README](../server/README.md)。 - **server→client 请求是未使用的功能**——传输层支持,但服务器从不发送;Python SDK 的应答接口为未来审批流程预留。 diff --git a/packages/scaffold/scripts/src/index.ts b/packages/scaffold/scripts/src/index.ts index 0af3ef0b30..0db62945f9 100644 --- a/packages/scaffold/scripts/src/index.ts +++ b/packages/scaffold/scripts/src/index.ts @@ -3,7 +3,7 @@ * * FIXME: rename to `@deepseek-ai/dsh-sdk-scripts` before the first tagged release — * the current name is indefensibly generic as a published name - * ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * ([regrouping Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)). * * @module @deepseek-ai/dsh-scripts */ diff --git a/packages/scaffold/server/README.i18n.yaml b/packages/scaffold/server/README.i18n.yaml index 46a18ec199..fc60d047d1 100644 --- a/packages/scaffold/server/README.i18n.yaml +++ b/packages/scaffold/server/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/scaffold/server/README.md -README.md: e0e06e27320ab41f131a1a36a7ccbd26271ce67c -README.zh.md: ed87a869180571a3cbb51c2ce92a0647e545ca3a +README.md: 0afb3a38ece57f74729a218dc85348fa6c7fa529 +README.zh.md: 5305434abbd83eb230dcdd81e72120d96da26cda diff --git a/packages/scaffold/server/src/index.ts b/packages/scaffold/server/src/index.ts index 9985cadfb4..f0261e1a21 100644 --- a/packages/scaffold/server/src/index.ts +++ b/packages/scaffold/server/src/index.ts @@ -8,7 +8,7 @@ * * FIXME: rename to `@deepseek-ai/dsh-sdk-server` before the first tagged release — * the current name says the wire encoding, not the role; it is the server half of - * the SDK protocol ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * the SDK protocol ([regrouping Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)). * * @module @deepseek-ai/dsh-jsonrpc */ diff --git a/packages/scaffold/telemetry/src/index.ts b/packages/scaffold/telemetry/src/index.ts index 17ea1d02c7..3c0a5beae7 100644 --- a/packages/scaffold/telemetry/src/index.ts +++ b/packages/scaffold/telemetry/src/index.ts @@ -9,7 +9,8 @@ * * FIXME: rename to `@deepseek-ai/dsh-sdk-telemetry` before the first tagged release — * the current name collides with the `dsh-session-telemetry` family; this is - * launcher-side SDK telemetry ([regrouping Agent Note](../../../../.agents/notes/proposed/architecture/2026-07-29-package-regrouping.md)). + * launcher-side SDK telemetry + * ([regrouping Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-29-package-regrouping.md)). * * @module @deepseek-ai/dsh-telemetry */ diff --git a/packages/subagent/subagent-codex/tsconfig.json b/packages/subagent/subagent-codex/tsconfig.json index b9f33967ba..3bb974477e 100644 --- a/packages/subagent/subagent-codex/tsconfig.json +++ b/packages/subagent/subagent-codex/tsconfig.json @@ -24,7 +24,7 @@ "path": "../../llm/llm" }, { - "path": "../../sdk/sdk-protocol" + "path": "../../scaffold/protocol" }, { "path": "../../core/session" diff --git a/packages/subagent/subagent/tsconfig.json b/packages/subagent/subagent/tsconfig.json index ccfecc02e4..c72f2ef68d 100644 --- a/packages/subagent/subagent/tsconfig.json +++ b/packages/subagent/subagent/tsconfig.json @@ -30,10 +30,10 @@ "path": "../../session/session-persistence" }, { - "path": "../../session-projection/session-projection" + "path": "../../session/session-projection" }, { - "path": "../../session-projection/session-projection-cache" + "path": "../../session/session-projection-cache" }, { "path": "../../tasks/tasks" diff --git a/packages/typert/generator/tests/type-model.spec.ts b/packages/typert/generator/tests/type-model.spec.ts index e1b5d99de9..f8c41fb157 100644 --- a/packages/typert/generator/tests/type-model.spec.ts +++ b/packages/typert/generator/tests/type-model.spec.ts @@ -1264,14 +1264,14 @@ function configureDualRuntimeClient(root: string, splitProjects: boolean): void const hostAggregate = JSON.parse(readFileSync(hostAggregatePath, 'utf8')) as { references: { path: string }[] } - hostAggregate.references.push({ path: './packages/client/tsconfig.host.json' }) + hostAggregate.references.push({ path: ['.', 'packages', 'client', 'tsconfig.host.json'].join('/') }) writeFileSync(hostAggregatePath, `${JSON.stringify(hostAggregate, null, 2)}\n`) const clientAggregatePath = join(root, 'tsconfig.client.json') const clientAggregate = JSON.parse(readFileSync(clientAggregatePath, 'utf8')) as { references: { path: string }[] } - clientAggregate.references = [{ path: './packages/client/tsconfig.client.json' }] + clientAggregate.references = [{ path: ['.', 'packages', 'client', 'tsconfig.client.json'].join('/') }] writeFileSync(clientAggregatePath, `${JSON.stringify(clientAggregate, null, 2)}\n`) } diff --git a/packages/util/environment/README.i18n.yaml b/packages/util/environment/README.i18n.yaml index 1c5f784bc4..633064ae4c 100644 --- a/packages/util/environment/README.i18n.yaml +++ b/packages/util/environment/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/util/environment/README.md -README.md: af6b0d9cc66b0bdfa1ad9ffb273260d0f4f06ddc -README.zh.md: 98c3c69ec96f835721e042960fe044fe075e6159 +README.md: 599a9ba747905b66452b57717dabcc6f4678a3dd +README.zh.md: 69f9c670bbb3ea1b2fbddaa7508a19dfb47fad12 diff --git a/packages/util/environment/README.md b/packages/util/environment/README.md index af6b0d9cc6..599a9ba747 100644 --- a/packages/util/environment/README.md +++ b/packages/util/environment/README.md @@ -32,5 +32,5 @@ const endpoint = environmentOf(ctx).get('DEEPSEEK_BASE_URL')?.value ## Known Limitations and Deferred Work -- **The snapshot is not a subprocess boundary** — every layer is also materialized into `process.env`, so ordinary project variables reach child processes under [`dsh-subprocess`](../../subprocess/subprocess/README.md)'s scrub. The product launcher's [`.env` contract](../../ui/app-boot/README.md#profiles) rejects bootstrap variables before materialization. +- **The snapshot is not a subprocess boundary** — every layer is also materialized into `process.env`, so ordinary project variables reach child processes under [`dsh-subprocess`](../../subprocess/subprocess/README.md)'s scrub. The product launcher's [`.env` contract](../../boot/app-boot/README.md#profiles) rejects bootstrap variables before materialization. - **No per-workspace layer** — the project layer is the *invoking* directory, fixed at launch. A workspace selected later in the Web UI contributes nothing, deliberately: following it would let a model's own workspace change the harness environment mid-session. diff --git a/packages/util/environment/README.zh.md b/packages/util/environment/README.zh.md index 98c3c69ec9..69f9c670bb 100644 --- a/packages/util/environment/README.zh.md +++ b/packages/util/environment/README.zh.md @@ -32,5 +32,5 @@ const endpoint = environmentOf(ctx).get('DEEPSEEK_BASE_URL')?.value ## Known Limitations and Deferred Work -- **快照不是子进程边界**:每一层同样会被物化进 `process.env`,因此项目里的普通变量会按 [`dsh-subprocess`](../../subprocess/subprocess/README.md) 的清洗规则抵达子进程。产品启动器的 [`.env` 契约](../../ui/app-boot/README.md#profiles) 会在物化之前拒绝 bootstrap 变量。 +- **快照不是子进程边界**:每一层同样会被物化进 `process.env`,因此项目里的普通变量会按 [`dsh-subprocess`](../../subprocess/subprocess/README.md) 的清洗规则抵达子进程。产品启动器的 [`.env` 契约](../../boot/app-boot/README.md#profiles) 会在物化之前拒绝 bootstrap 变量。 - **没有按工作区划分的层**:项目层是*调用*目录,在启动时固定。之后在 Web UI 中选择的工作区不贡献任何内容,这是刻意的:跟随它等于让模型自己的工作区在会话中途改变 harness 的环境。 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a92883786a..e9726e9bee 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -142,7 +142,7 @@ importers: version: link:../../vendor/timer '@deepseek-ai/dsh-app-boot': specifier: workspace:^ - version: link:../../packages/ui/app-boot + version: link:../../packages/boot/app-boot '@deepseek-ai/dsh-base': specifier: workspace:^ version: link:../../packages/bundle/base @@ -169,13 +169,13 @@ importers: version: link:../../packages/context/tmux-context '@deepseek-ai/dsh-tool-ask-user': specifier: workspace:^ - version: link:../../packages/ui/tool-ask-user + version: link:../../packages/interaction/tool-ask-user '@deepseek-ai/dsh-tool-bash-persistent': specifier: workspace:^ version: link:../../packages/pty/tool-bash-persistent '@deepseek-ai/dsh-tool-cordis': specifier: workspace:^ - version: link:../../packages/cordis/tool-cordis + version: link:../../packages/self-modification/tool-cordis '@deepseek-ai/dsh-web-app': specifier: workspace:^ version: link:../../packages/bundle/web-app @@ -309,7 +309,7 @@ importers: version: link:../packages/examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:* - version: link:../packages/ui/app-boot + version: link:../packages/boot/app-boot '@deepseek-ai/dsh-bash': specifier: workspace:* version: link:../packages/bash/bash @@ -333,7 +333,7 @@ importers: version: link:../packages/goal/command-goal '@deepseek-ai/dsh-commands': specifier: workspace:* - version: link:../packages/ui/commands + version: link:../packages/interaction/commands '@deepseek-ai/dsh-compact': specifier: workspace:* version: link:../packages/compact/compact @@ -378,7 +378,7 @@ importers: version: link:../packages/support/invariants '@deepseek-ai/dsh-jsonrpc': specifier: workspace:* - version: link:../packages/ui/jsonrpc + version: link:../packages/scaffold/server '@deepseek-ai/dsh-llm': specifier: workspace:* version: link:../packages/llm/llm @@ -402,7 +402,7 @@ importers: version: link:../packages/lsp/lsp-local '@deepseek-ai/dsh-permission': specifier: workspace:* - version: link:../packages/ui/permission + version: link:../packages/interaction/permission '@deepseek-ai/dsh-plan-mode': specifier: workspace:* version: link:../packages/plan/plan-mode @@ -420,7 +420,7 @@ importers: version: link:../packages/guard/repeat-tool-guard '@deepseek-ai/dsh-repository-plugin': specifier: workspace:* - version: link:../packages/cordis/repository-plugin + version: link:../packages/self-modification/repository-plugin '@deepseek-ai/dsh-sandbox': specifier: workspace:* version: link:../packages/sandbox/sandbox @@ -438,13 +438,13 @@ importers: version: link:../packages/core/session '@deepseek-ai/dsh-session-checkpoint-policy': specifier: workspace:* - version: link:../packages/session-persistence/session-checkpoint-policy + version: link:../packages/session/session-checkpoint-policy '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:* - version: link:../packages/session-persistence/session-persistence-jsonl + version: link:../packages/session/session-persistence-jsonl '@deepseek-ai/dsh-session-projection': specifier: workspace:* - version: link:../packages/session-projection/session-projection + version: link:../packages/session/session-projection '@deepseek-ai/dsh-session-query': specifier: workspace:* version: link:../packages/session-query/session-query @@ -456,13 +456,13 @@ importers: version: link:../packages/context/session-reference '@deepseek-ai/dsh-session-telemetry-otel': specifier: workspace:* - version: link:../packages/telemetry/session-telemetry-otel + version: link:../packages/session/session-telemetry-otel '@deepseek-ai/dsh-session-title': specifier: workspace:* - version: link:../packages/session-title/session-title + version: link:../packages/session/session-title '@deepseek-ai/dsh-session-title-first-message-llm': specifier: workspace:* - version: link:../packages/session-title/session-title-first-message-llm + version: link:../packages/session/session-title-first-message-llm '@deepseek-ai/dsh-settings-local': specifier: workspace:* version: link:../packages/settings/settings-local @@ -516,13 +516,13 @@ importers: version: link:../packages/context/time-context '@deepseek-ai/dsh-timeout-policy': specifier: workspace:* - version: link:../packages/timeout/timeout-policy + version: link:../packages/guard/timeout-policy '@deepseek-ai/dsh-token-meter': specifier: workspace:* version: link:../packages/llm/token-meter '@deepseek-ai/dsh-tool-ask-user': specifier: workspace:* - version: link:../packages/ui/tool-ask-user + version: link:../packages/interaction/tool-ask-user '@deepseek-ai/dsh-tool-bash': specifier: workspace:* version: link:../packages/bash/tool-bash @@ -531,7 +531,7 @@ importers: version: link:../packages/pty/tool-bash-persistent '@deepseek-ai/dsh-tool-cordis': specifier: workspace:* - version: link:../packages/cordis/tool-cordis + version: link:../packages/self-modification/tool-cordis '@deepseek-ai/dsh-tool-fs': specifier: workspace:* version: link:../packages/fs/tool-fs @@ -588,10 +588,10 @@ importers: version: link:../packages/core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:* - version: link:../packages/ui/user-approval + version: link:../packages/interaction/user-approval '@deepseek-ai/dsh-user-interaction': specifier: workspace:* - version: link:../packages/ui/user-interaction + version: link:../packages/interaction/user-interaction '@deepseek-ai/dsh-web': specifier: workspace:* version: link:../packages/web/web @@ -665,7 +665,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -715,7 +715,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-typert-registry': specifier: workspace:^ version: link:../../typert/registry @@ -761,7 +761,7 @@ importers: version: link:../../util/paths '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -890,7 +890,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-subprocess-local': specifier: workspace:^ version: link:../../subprocess/subprocess-local @@ -911,7 +911,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -965,6 +965,43 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis + packages/boot/app-boot: + dependencies: + js-yaml: + specifier: ^4.2.0 + version: 4.2.0 + devDependencies: + '@cordisjs/plugin-hmr': + specifier: workspace:^ + version: link:../../../vendor/hmr + '@cordisjs/plugin-include': + specifier: workspace:^ + version: link:../../../vendor/include + '@cordisjs/plugin-loader': + specifier: workspace:^ + version: link:../../../vendor/loader + '@cordisjs/plugin-timer': + specifier: workspace:^ + version: link:../../../vendor/timer + '@deepseek-ai/dsh-environment': + specifier: workspace:^ + version: link:../../util/environment + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-paths': + specifier: workspace:^ + version: link:../../util/paths + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + packages/bundle/base: dependencies: '@cordisjs/plugin-hmr': @@ -999,7 +1036,7 @@ importers: version: link:../../goal/command-goal '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-compact-basic': specifier: workspace:^ version: link:../../compact/compact-basic @@ -1035,7 +1072,7 @@ importers: version: link:../../llm/llm-retry '@deepseek-ai/dsh-permission': specifier: workspace:^ - version: link:../../ui/permission + version: link:../../interaction/permission '@deepseek-ai/dsh-plan-mode': specifier: workspace:^ version: link:../../plan/plan-mode @@ -1044,7 +1081,7 @@ importers: version: link:../../guard/repeat-tool-guard '@deepseek-ai/dsh-repository-plugin': specifier: workspace:^ - version: link:../../cordis/repository-plugin + version: link:../../self-modification/repository-plugin '@deepseek-ai/dsh-sandbox-local': specifier: workspace:^ version: link:../../sandbox/sandbox-local @@ -1056,25 +1093,25 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-checkpoint-policy': specifier: workspace:^ - version: link:../../session-persistence/session-checkpoint-policy + version: link:../../session/session-checkpoint-policy '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-session-query-sqlite': specifier: workspace:^ version: link:../../session-query/session-query-sqlite '@deepseek-ai/dsh-session-telemetry-otel': specifier: workspace:^ - version: link:../../telemetry/session-telemetry-otel + version: link:../../session/session-telemetry-otel '@deepseek-ai/dsh-session-title': specifier: workspace:^ - version: link:../../session-title/session-title + version: link:../../session/session-title '@deepseek-ai/dsh-session-title-first-message-llm': specifier: workspace:^ - version: link:../../session-title/session-title-first-message-llm + version: link:../../session/session-title-first-message-llm '@deepseek-ai/dsh-settings-local': specifier: workspace:^ version: link:../../settings/settings-local @@ -1113,7 +1150,7 @@ importers: version: link:../../tasks/tasks-local '@deepseek-ai/dsh-timeout-policy': specifier: workspace:^ - version: link:../../timeout/timeout-policy + version: link:../../guard/timeout-policy '@deepseek-ai/dsh-token-meter': specifier: workspace:^ version: link:../../llm/token-meter @@ -1170,10 +1207,10 @@ importers: version: link:../../typert/registry '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../ui/user-interaction + version: link:../../interaction/user-interaction '@deepseek-ai/dsh-web': specifier: workspace:^ version: link:../../web/web @@ -1328,7 +1365,7 @@ importers: version: link:../../host/webserver '@deepseek-ai/dsh-session-projection-cache': specifier: workspace:^ - version: link:../../session-projection/session-projection-cache + version: link:../../session/session-projection-cache '@deepseek-ai/dsh-storage': specifier: workspace:^ version: link:../../storage/storage @@ -1362,7 +1399,7 @@ importers: dependencies: '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-host-apiproxy': specifier: workspace:^ version: link:../../host/apiproxy @@ -1466,7 +1503,7 @@ importers: version: link:../ui-slots '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-compact': specifier: workspace:^ version: link:../../compact/compact @@ -1484,10 +1521,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-session-title': specifier: workspace:^ - version: link:../../session-title/session-title + version: link:../../session/session-title immer: specifier: ^10.1.1 version: 10.2.0 @@ -1651,13 +1688,13 @@ importers: version: link:../../support/invariants '@deepseek-ai/dsh-permission': specifier: workspace:^ - version: link:../../ui/permission + version: link:../../interaction/permission '@deepseek-ai/dsh-plan-mode': specifier: workspace:^ version: link:../../plan/plan-mode '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-token-meter': specifier: workspace:^ version: link:../../llm/token-meter @@ -1895,7 +1932,7 @@ importers: version: link:../../support/invariants '@deepseek-ai/dsh-permission': specifier: workspace:^ - version: link:../../ui/permission + version: link:../../interaction/permission '@types/react': specifier: ~18.3.1 version: 18.3.31 @@ -2043,7 +2080,7 @@ importers: version: link:../ui-slots '@deepseek-ai/dsh-tool-ask-user': specifier: workspace:^ - version: link:../../ui/tool-ask-user + version: link:../../interaction/tool-ask-user clsx: specifier: ^2.0.0 version: 2.1.1 @@ -2068,7 +2105,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../ui/user-interaction + version: link:../../interaction/user-interaction '@types/react': specifier: ~18.3.1 version: 18.3.31 @@ -2614,7 +2651,7 @@ importers: version: link:../../core/agent '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-compact': specifier: workspace:^ version: link:../compact @@ -2871,83 +2908,6 @@ importers: specifier: ^4.0.0-rc.6 version: link:../../../vendor/cordis - packages/cordis/repository-plugin: - dependencies: - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@cordisjs/plugin-loader': - specifier: workspace:^ - version: link:../../../vendor/loader - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-mcp-client': - specifier: workspace:^ - version: link:../../mcp/mcp-client - '@deepseek-ai/dsh-paths': - specifier: workspace:^ - version: link:../../util/paths - '@deepseek-ai/dsh-skill': - specifier: workspace:^ - version: link:../../skill/skill - '@deepseek-ai/dsh-skill-local': - specifier: workspace:^ - version: link:../../skill/skill-local - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/cordis/tool-cordis: - dependencies: - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery - devDependencies: - '@cordisjs/plugin-loader': - specifier: ^1.0.0-rc.5 - version: link:../../../vendor/loader - '@cordisjs/plugin-timer': - specifier: workspace:^ - version: link:../../../vendor/timer - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-agent-loop': - specifier: workspace:^ - version: link:../../core/agent-loop - '@deepseek-ai/dsh-agent-loop-testkit': - specifier: workspace:^ - version: link:../../support/agent-loop-testkit - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-scope': - specifier: workspace:^ - version: link:../../core/scope - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - packages/core/agent: devDependencies: '@deepseek-ai/dsh-invariants': @@ -2998,10 +2958,10 @@ importers: version: link:../session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-system-prompt': specifier: workspace:^ version: link:../system-prompt @@ -3093,7 +3053,7 @@ importers: version: link:../system-prompt '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -3219,16 +3179,16 @@ importers: version: link:../agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ - version: link:../../ui/app-boot + version: link:../../boot/app-boot '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants '@deepseek-ai/dsh-session-checkpoint-policy': specifier: workspace:^ - version: link:../../session-persistence/session-checkpoint-policy + version: link:../../session/session-checkpoint-policy '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-session-query': specifier: workspace:^ version: link:../../session-query/session-query @@ -3316,7 +3276,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-title': specifier: workspace:^ - version: link:../../session-title/session-title + version: link:../../session/session-title '@deepseek-ai/dsh-skill': specifier: workspace:^ version: link:../../skill/skill @@ -3367,7 +3327,7 @@ importers: dependencies: '@deepseek-ai/dsh-app-boot': specifier: workspace:^ - version: link:../../ui/app-boot + version: link:../../boot/app-boot devDependencies: '@deepseek-ai/dsh-invariants': specifier: workspace:^ @@ -3389,7 +3349,7 @@ importers: version: link:../../core/agent '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -3532,7 +3492,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -3639,7 +3599,7 @@ importers: version: link:../../core/agent '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-goal': specifier: workspace:^ version: link:../goal @@ -3688,7 +3648,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-type-meta': specifier: workspace:^ version: link:../../typert/type-meta @@ -3794,6 +3754,24 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis + packages/guard/timeout-policy: + devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-timeout': + specifier: workspace:^ + version: link:../../util/timeout + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + packages/hooks/hook-protocol: devDependencies: '@deepseek-ai/dsh-bash': @@ -3844,10 +3822,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../../subagent/subagent @@ -3896,10 +3874,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-subprocess-local': specifier: workspace:^ version: link:../../subprocess/subprocess-local @@ -3923,7 +3901,7 @@ importers: version: link:../../util/brand '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-credentials': specifier: workspace:^ version: link:../../credentials/credentials @@ -3944,19 +3922,19 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-session-projection-cache': specifier: workspace:^ - version: link:../../session-projection/session-projection-cache + version: link:../../session/session-projection-cache '@deepseek-ai/dsh-session-query': specifier: workspace:^ version: link:../../session-query/session-query '@deepseek-ai/dsh-session-title': specifier: workspace:^ - version: link:../../session-title/session-title + version: link:../../session/session-title '@deepseek-ai/dsh-settings': specifier: workspace:^ version: link:../../settings/settings @@ -3971,10 +3949,10 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../ui/user-interaction + version: link:../../interaction/user-interaction '@deepseek-ai/dsh-workspace': specifier: workspace:^ version: link:../../workspace/workspace @@ -4152,6 +4130,137 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis + packages/interaction/commands: + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-brand': + specifier: workspace:^ + version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/interaction/permission: + dependencies: + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@deepseek-ai/dsh-bash': + specifier: workspace:^ + version: link:../../bash/bash + '@deepseek-ai/dsh-commands': + specifier: workspace:^ + version: link:../commands + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-sandbox': + specifier: workspace:^ + version: link:../../sandbox/sandbox + '@deepseek-ai/dsh-sandbox-policy': + specifier: workspace:^ + version: link:../../sandbox/sandbox-policy + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-projection': + specifier: workspace:^ + version: link:../../session/session-projection + '@deepseek-ai/dsh-settings': + specifier: workspace:^ + version: link:../../settings/settings + '@deepseek-ai/dsh-user-approval': + specifier: workspace:^ + version: link:../user-approval + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/interaction/tool-ask-user: + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + '@deepseek-ai/dsh-user-interaction': + specifier: workspace:^ + version: link:../user-interaction + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/interaction/user-approval: + dependencies: + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-brand': + specifier: workspace:^ + version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/interaction/user-interaction: + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + packages/llm/llm: dependencies: schemastery: @@ -4274,10 +4383,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-session-persistence-sqlite': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-sqlite + version: link:../../session/session-persistence-sqlite '@deepseek-ai/dsh-system-prompt': specifier: workspace:^ version: link:../../core/system-prompt @@ -4314,7 +4423,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -4415,7 +4524,7 @@ importers: version: link:../../util/timeout '@deepseek-ai/dsh-timeout-policy': specifier: workspace:^ - version: link:../../timeout/timeout-policy + version: link:../../guard/timeout-policy '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -4474,7 +4583,7 @@ importers: version: link:../../code-runtime/code-runtime '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../ui/commands + version: link:../../interaction/commands '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -4486,7 +4595,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-system-prompt': specifier: workspace:^ version: link:../../core/system-prompt @@ -4495,7 +4604,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../ui/user-interaction + version: link:../../interaction/user-interaction cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -4724,7 +4833,25 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/sdk/create-sdk: + packages/scaffold/client: + devDependencies: + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-sdk-protocol': + specifier: workspace:^ + version: link:../protocol + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/scaffold/create-sdk: dependencies: '@deepseek-ai/dsh-helper': specifier: workspace:^ @@ -4740,7 +4867,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/sdk/helper: + packages/scaffold/helper: dependencies: '@clack/core': specifier: ^1.4.3 @@ -4772,10 +4899,10 @@ importers: version: link:../../support/invariants '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-session-persistence-sqlite': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-sqlite + version: link:../../session/session-persistence-sqlite '@deepseek-ai/dsh-subprocess': specifier: workspace:^ version: link:../../subprocess/subprocess @@ -4792,56 +4919,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/sdk/scripts: - dependencies: - '@deepseek-ai/dsh-helper': - specifier: workspace:^ - version: link:../helper - '@deepseek-ai/dsh-telemetry': - specifier: workspace:^ - version: link:../telemetry - commander: - specifier: ^15.0.0 - version: 15.0.0 - node-addon-require-builtin: - specifier: ^0.1.4 - version: 0.1.4 - devDependencies: - '@deepseek-ai/dsh-app-boot': - specifier: workspace:^ - version: link:../../ui/app-boot - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - tsdown: - specifier: ^0.22.2 - version: 0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3) - tsx: - specifier: ^4.22.4 - version: 4.22.4 - - packages/sdk/sdk-client: - devDependencies: - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-sdk-protocol': - specifier: workspace:^ - version: link:../sdk-protocol - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/sdk/sdk-protocol: + packages/scaffold/protocol: devDependencies: '@deepseek-ai/dsh-invariants': specifier: workspace:^ @@ -4859,7 +4937,81 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/sdk/telemetry: + packages/scaffold/scripts: + dependencies: + '@deepseek-ai/dsh-helper': + specifier: workspace:^ + version: link:../helper + '@deepseek-ai/dsh-telemetry': + specifier: workspace:^ + version: link:../telemetry + commander: + specifier: ^15.0.0 + version: 15.0.0 + node-addon-require-builtin: + specifier: ^0.1.4 + version: 0.1.4 + devDependencies: + '@deepseek-ai/dsh-app-boot': + specifier: workspace:^ + version: link:../../boot/app-boot + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + tsdown: + specifier: ^0.22.2 + version: 0.22.2(oxc-resolver@11.20.0)(publint@0.3.21)(tsx@4.22.4)(typescript@6.0.3) + tsx: + specifier: ^4.22.4 + version: 4.22.4 + + packages/scaffold/server: + dependencies: + schemastery: + specifier: ^3.17.0 + version: link:../../../vendor/schemastery + devDependencies: + '@cordisjs/plugin-loader': + specifier: workspace:^ + version: link:../../../vendor/loader + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-spine-demo': + specifier: workspace:^ + version: link:../../examples/agent-spine-demo + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-llm-deepseek': + specifier: workspace:^ + version: link:../../llm/llm-deepseek + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope + '@deepseek-ai/dsh-sdk-protocol': + specifier: workspace:^ + version: link:../protocol + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-persistence-jsonl': + specifier: workspace:^ + version: link:../../session/session-persistence-jsonl + '@deepseek-ai/dsh-subagent': + specifier: workspace:^ + version: link:../../subagent/subagent + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/scaffold/telemetry: dependencies: yaml: specifier: ^2.9.0 @@ -4878,7 +5030,185 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-persistence/session-checkpoint-policy: + packages/self-modification/repository-plugin: + dependencies: + zod: + specifier: ^4.4.3 + version: 4.4.3 + devDependencies: + '@cordisjs/plugin-loader': + specifier: workspace:^ + version: link:../../../vendor/loader + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-mcp-client': + specifier: workspace:^ + version: link:../../mcp/mcp-client + '@deepseek-ai/dsh-paths': + specifier: workspace:^ + version: link:../../util/paths + '@deepseek-ai/dsh-skill': + specifier: workspace:^ + version: link:../../skill/skill + '@deepseek-ai/dsh-skill-local': + specifier: workspace:^ + version: link:../../skill/skill-local + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/self-modification/tool-cordis: + dependencies: + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + devDependencies: + '@cordisjs/plugin-loader': + specifier: ^1.0.0-rc.5 + version: link:../../../vendor/loader + '@cordisjs/plugin-timer': + specifier: workspace:^ + version: link:../../../vendor/timer + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-loop': + specifier: workspace:^ + version: link:../../core/agent-loop + '@deepseek-ai/dsh-agent-loop-testkit': + specifier: workspace:^ + version: link:../../support/agent-loop-testkit + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-scope': + specifier: workspace:^ + version: link:../../core/scope + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/session-query/session-query: + devDependencies: + '@deepseek-ai/dsh-brand': + specifier: workspace:^ + version: link:../../util/brand + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-persistence': + specifier: workspace:^ + version: link:../../session/session-persistence + '@deepseek-ai/dsh-session-title': + specifier: workspace:^ + version: link:../../session/session-title + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/session-query/session-query-sqlite: + dependencies: + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + devDependencies: + '@cordisjs/plugin-loader': + specifier: workspace:^ + version: link:../../../vendor/loader + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-persistence': + specifier: workspace:^ + version: link:../../session/session-persistence + '@deepseek-ai/dsh-session-persistence-sqlite': + specifier: workspace:^ + version: link:../../session/session-persistence-sqlite + '@deepseek-ai/dsh-session-query': + specifier: workspace:^ + version: link:../session-query + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/session-query/tool-session-query: + dependencies: + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-persistence': + specifier: workspace:^ + version: link:../../session/session-persistence + '@deepseek-ai/dsh-session-persistence-jsonl': + specifier: workspace:^ + version: link:../../session/session-persistence-jsonl + '@deepseek-ai/dsh-session-query': + specifier: workspace:^ + version: link:../session-query + '@deepseek-ai/dsh-session-query-sqlite': + specifier: workspace:^ + version: link:../session-query-sqlite + '@deepseek-ai/dsh-session-title': + specifier: workspace:^ + version: link:../../session/session-title + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-timeout': + specifier: workspace:^ + version: link:../../util/timeout + '@deepseek-ai/dsh-timeout-policy': + specifier: workspace:^ + version: link:../../guard/timeout-policy + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/session/session-checkpoint-policy: devDependencies: '@cordisjs/plugin-loader': specifier: workspace:^ @@ -4917,7 +5247,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-persistence/session-persistence: + packages/session/session-persistence: devDependencies: '@deepseek-ai/dsh-brand': specifier: workspace:^ @@ -4938,7 +5268,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-persistence/session-persistence-jsonl: + packages/session/session-persistence-jsonl: dependencies: koffi: specifier: ^3.1.0 @@ -4960,7 +5290,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-persistence/session-persistence-sqlite: + packages/session/session-persistence-sqlite: dependencies: schemastery: specifier: ^3.18.0 @@ -4979,7 +5309,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-projection/session-projection: + packages/session/session-projection: dependencies: zod: specifier: ^4.4.3 @@ -4995,7 +5325,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-projection/session-projection-cache: + packages/session/session-projection-cache: dependencies: schemastery: specifier: ^3.18.0 @@ -5012,7 +5342,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../session-persistence '@deepseek-ai/dsh-session-projection': specifier: workspace:^ version: link:../session-projection @@ -5026,63 +5356,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-query/session-query: - devDependencies: - '@deepseek-ai/dsh-brand': - specifier: workspace:^ - version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-persistence': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence - '@deepseek-ai/dsh-session-title': - specifier: workspace:^ - version: link:../../session-title/session-title - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/session-query/session-query-sqlite: - dependencies: - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery - devDependencies: - '@cordisjs/plugin-loader': - specifier: workspace:^ - version: link:../../../vendor/loader - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-persistence': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence - '@deepseek-ai/dsh-session-persistence-sqlite': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence-sqlite - '@deepseek-ai/dsh-session-query': - specifier: workspace:^ - version: link:../session-query - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/session-query/tool-session-query: - dependencies: - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery + packages/session/session-telemetry: devDependencies: '@deepseek-ai/dsh-agent': specifier: workspace:^ @@ -5090,44 +5364,66 @@ importers: '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session - '@deepseek-ai/dsh-session-persistence': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence - '@deepseek-ai/dsh-session-persistence-jsonl': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl - '@deepseek-ai/dsh-session-query': - specifier: workspace:^ - version: link:../session-query - '@deepseek-ai/dsh-session-query-sqlite': - specifier: workspace:^ - version: link:../session-query-sqlite - '@deepseek-ai/dsh-session-title': - specifier: workspace:^ - version: link:../../session-title/session-title - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-timeout': - specifier: workspace:^ - version: link:../../util/timeout - '@deepseek-ai/dsh-timeout-policy': - specifier: workspace:^ - version: link:../../timeout/timeout-policy - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-title/session-title: + packages/session/session-telemetry-otel: + dependencies: + '@opentelemetry/api': + specifier: ^1.9.1 + version: 1.9.1 + '@opentelemetry/api-logs': + specifier: ^0.220.0 + version: 0.220.0 + '@opentelemetry/exporter-logs-otlp-http': + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': + specifier: ^2.9.0 + version: 2.10.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': + specifier: ^0.220.0 + version: 0.220.0(@opentelemetry/api@1.9.1) + schemastery: + specifier: ^3.18.0 + version: link:../../../vendor/schemastery + devDependencies: + '@cordisjs/plugin-loader': + specifier: workspace:^ + version: link:../../../vendor/loader + '@deepseek-ai/dsh-brand': + specifier: workspace:^ + version: link:../../util/brand + '@deepseek-ai/dsh-command-feedback': + specifier: workspace:^ + version: link:../../feedback/command-feedback + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-paths': + specifier: workspace:^ + version: link:../../util/paths + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-telemetry': + specifier: workspace:^ + version: link:../session-telemetry + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + + packages/session/session-title: dependencies: schemastery: specifier: ^3.18.0 @@ -5150,18 +5446,18 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../session-persistence-jsonl '@deepseek-ai/dsh-session-persistence-sqlite': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-sqlite + version: link:../session-persistence-sqlite '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../session-projection cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-title/session-title-all-messages-llm: + packages/session/session-title-all-messages-llm: dependencies: schemastery: specifier: ^3.18.0 @@ -5186,7 +5482,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-title/session-title-first-message-llm: + packages/session/session-title-first-message-llm: dependencies: schemastery: specifier: ^3.18.0 @@ -5220,7 +5516,7 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/session-title/session-title-llm: + packages/session/session-title-llm: dependencies: schemastery: specifier: ^3.18.0 @@ -5541,13 +5837,13 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-session-projection-cache': specifier: workspace:^ - version: link:../../session-projection/session-projection-cache + version: link:../../session/session-projection-cache '@deepseek-ai/dsh-storage': specifier: workspace:^ version: link:../../storage/storage @@ -5673,7 +5969,7 @@ importers: version: link:../../support/loader-smoke '@deepseek-ai/dsh-sdk-protocol': specifier: workspace:^ - version: link:../../sdk/sdk-protocol + version: link:../../scaffold/protocol '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -5719,10 +6015,10 @@ importers: version: link:../../support/loader-smoke '@deepseek-ai/dsh-sdk-client': specifier: workspace:^ - version: link:../../sdk/sdk-client + version: link:../../scaffold/client '@deepseek-ai/dsh-sdk-protocol': specifier: workspace:^ - version: link:../../sdk/sdk-protocol + version: link:../../scaffold/protocol '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../core/session @@ -5816,7 +6112,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../ui/user-approval + version: link:../../interaction/user-approval cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -5896,10 +6192,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent @@ -5947,13 +6243,13 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent @@ -5993,10 +6289,10 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl + version: link:../../session/session-persistence-jsonl '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent @@ -6231,91 +6527,6 @@ importers: specifier: ^4.0.0-rc.6 version: link:../../../vendor/cordis - packages/telemetry/session-telemetry: - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/telemetry/session-telemetry-otel: - dependencies: - '@opentelemetry/api': - specifier: ^1.9.1 - version: 1.9.1 - '@opentelemetry/api-logs': - specifier: ^0.220.0 - version: 0.220.0 - '@opentelemetry/exporter-logs-otlp-http': - specifier: ^0.220.0 - version: 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base': - specifier: ^0.220.0 - version: 0.220.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources': - specifier: ^2.9.0 - version: 2.10.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-logs': - specifier: ^0.220.0 - version: 0.220.0(@opentelemetry/api@1.9.1) - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery - devDependencies: - '@cordisjs/plugin-loader': - specifier: workspace:^ - version: link:../../../vendor/loader - '@deepseek-ai/dsh-brand': - specifier: workspace:^ - version: link:../../util/brand - '@deepseek-ai/dsh-command-feedback': - specifier: workspace:^ - version: link:../../feedback/command-feedback - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-paths': - specifier: workspace:^ - version: link:../../util/paths - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-telemetry': - specifier: workspace:^ - version: link:../session-telemetry - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/timeout/timeout-policy: - devDependencies: - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-timeout': - specifier: workspace:^ - version: link:../../util/timeout - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - packages/todo/tool-todo: dependencies: schemastery: @@ -6354,7 +6565,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../session-projection/session-projection + version: link:../../session/session-projection '@deepseek-ai/dsh-system-prompt': specifier: workspace:^ version: link:../../core/system-prompt @@ -6363,7 +6574,7 @@ importers: version: link:../../core/tools '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../ui/user-interaction + version: link:../../interaction/user-interaction cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis @@ -6382,7 +6593,7 @@ importers: version: link:../../support/invariants '@deepseek-ai/dsh-tool-cordis': specifier: workspace:^ - version: link:../../cordis/tool-cordis + version: link:../../self-modification/tool-cordis '@deepseek-ai/dsh-typert-registry': specifier: workspace:^ version: link:../registry @@ -6440,217 +6651,6 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/ui/app-boot: - dependencies: - js-yaml: - specifier: ^4.2.0 - version: 4.2.0 - devDependencies: - '@cordisjs/plugin-hmr': - specifier: workspace:^ - version: link:../../../vendor/hmr - '@cordisjs/plugin-include': - specifier: workspace:^ - version: link:../../../vendor/include - '@cordisjs/plugin-loader': - specifier: workspace:^ - version: link:../../../vendor/loader - '@cordisjs/plugin-timer': - specifier: workspace:^ - version: link:../../../vendor/timer - '@deepseek-ai/dsh-environment': - specifier: workspace:^ - version: link:../../util/environment - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-paths': - specifier: workspace:^ - version: link:../../util/paths - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@types/js-yaml': - specifier: ^4.0.9 - version: 4.0.9 - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/commands: - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-brand': - specifier: workspace:^ - version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-scope': - specifier: workspace:^ - version: link:../../core/scope - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/jsonrpc: - dependencies: - schemastery: - specifier: ^3.17.0 - version: link:../../../vendor/schemastery - devDependencies: - '@cordisjs/plugin-loader': - specifier: workspace:^ - version: link:../../../vendor/loader - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-agent-spine-demo': - specifier: workspace:^ - version: link:../../examples/agent-spine-demo - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-llm-deepseek': - specifier: workspace:^ - version: link:../../llm/llm-deepseek - '@deepseek-ai/dsh-scope': - specifier: workspace:^ - version: link:../../core/scope - '@deepseek-ai/dsh-sdk-protocol': - specifier: workspace:^ - version: link:../../sdk/sdk-protocol - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-persistence-jsonl': - specifier: workspace:^ - version: link:../../session-persistence/session-persistence-jsonl - '@deepseek-ai/dsh-subagent': - specifier: workspace:^ - version: link:../../subagent/subagent - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/permission: - dependencies: - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery - zod: - specifier: ^4.4.3 - version: 4.4.3 - devDependencies: - '@deepseek-ai/dsh-bash': - specifier: workspace:^ - version: link:../../bash/bash - '@deepseek-ai/dsh-commands': - specifier: workspace:^ - version: link:../commands - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-sandbox': - specifier: workspace:^ - version: link:../../sandbox/sandbox - '@deepseek-ai/dsh-sandbox-policy': - specifier: workspace:^ - version: link:../../sandbox/sandbox-policy - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-session-projection': - specifier: workspace:^ - version: link:../../session-projection/session-projection - '@deepseek-ai/dsh-settings': - specifier: workspace:^ - version: link:../../settings/settings - '@deepseek-ai/dsh-user-approval': - specifier: workspace:^ - version: link:../user-approval - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/tool-ask-user: - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - '@deepseek-ai/dsh-tools': - specifier: workspace:^ - version: link:../../core/tools - '@deepseek-ai/dsh-user-interaction': - specifier: workspace:^ - version: link:../user-interaction - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/user-approval: - dependencies: - schemastery: - specifier: ^3.18.0 - version: link:../../../vendor/schemastery - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-brand': - specifier: workspace:^ - version: link:../../util/brand - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - '@deepseek-ai/dsh-scope': - specifier: workspace:^ - version: link:../../core/scope - '@deepseek-ai/dsh-session': - specifier: workspace:^ - version: link:../../core/session - '@deepseek-ai/dsh-system-prompt': - specifier: workspace:^ - version: link:../../core/system-prompt - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - - packages/ui/user-interaction: - devDependencies: - '@deepseek-ai/dsh-agent': - specifier: workspace:^ - version: link:../../core/agent - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-llm': - specifier: workspace:^ - version: link:../../llm/llm - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - packages/util/atomic-write: devDependencies: '@deepseek-ai/dsh-invariants': @@ -6749,7 +6749,7 @@ importers: version: link:../../core/system-prompt '@deepseek-ai/dsh-timeout-policy': specifier: workspace:^ - version: link:../../timeout/timeout-policy + version: link:../../guard/timeout-policy '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools @@ -7049,7 +7049,7 @@ importers: version: link:../../core/session '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../session-persistence/session-persistence + version: link:../../session/session-persistence '@deepseek-ai/dsh-storage': specifier: workspace:^ version: link:../../storage/storage @@ -7085,7 +7085,7 @@ importers: version: link:../../packages/examples/agent-spine-demo '@deepseek-ai/dsh-app-boot': specifier: workspace:^ - version: link:../../packages/ui/app-boot + version: link:../../packages/boot/app-boot '@deepseek-ai/dsh-bash': specifier: workspace:^ version: link:../../packages/bash/bash @@ -7109,7 +7109,7 @@ importers: version: link:../../packages/goal/command-goal '@deepseek-ai/dsh-commands': specifier: workspace:^ - version: link:../../packages/ui/commands + version: link:../../packages/interaction/commands '@deepseek-ai/dsh-compact': specifier: workspace:^ version: link:../../packages/compact/compact @@ -7154,7 +7154,7 @@ importers: version: link:../../packages/support/invariants '@deepseek-ai/dsh-jsonrpc': specifier: workspace:^ - version: link:../../packages/ui/jsonrpc + version: link:../../packages/scaffold/server '@deepseek-ai/dsh-jsonrpc-demo': specifier: workspace:^ version: link:../../packages/examples/jsonrpc-demo @@ -7175,7 +7175,7 @@ importers: version: link:../../packages/util/paths '@deepseek-ai/dsh-permission': specifier: workspace:^ - version: link:../../packages/ui/permission + version: link:../../packages/interaction/permission '@deepseek-ai/dsh-plan-mode': specifier: workspace:^ version: link:../../packages/plan/plan-mode @@ -7205,25 +7205,25 @@ importers: version: link:../../packages/core/scope '@deepseek-ai/dsh-sdk-protocol': specifier: workspace:^ - version: link:../../packages/sdk/sdk-protocol + version: link:../../packages/scaffold/protocol '@deepseek-ai/dsh-session': specifier: workspace:^ version: link:../../packages/core/session '@deepseek-ai/dsh-session-checkpoint-policy': specifier: workspace:^ - version: link:../../packages/session-persistence/session-checkpoint-policy + version: link:../../packages/session/session-checkpoint-policy '@deepseek-ai/dsh-session-persistence': specifier: workspace:^ - version: link:../../packages/session-persistence/session-persistence + version: link:../../packages/session/session-persistence '@deepseek-ai/dsh-session-persistence-jsonl': specifier: workspace:^ - version: link:../../packages/session-persistence/session-persistence-jsonl + version: link:../../packages/session/session-persistence-jsonl '@deepseek-ai/dsh-session-persistence-sqlite': specifier: workspace:^ - version: link:../../packages/session-persistence/session-persistence-sqlite + version: link:../../packages/session/session-persistence-sqlite '@deepseek-ai/dsh-session-projection': specifier: workspace:^ - version: link:../../packages/session-projection/session-projection + version: link:../../packages/session/session-projection '@deepseek-ai/dsh-session-query': specifier: workspace:^ version: link:../../packages/session-query/session-query @@ -7235,7 +7235,7 @@ importers: version: link:../../packages/context/session-reference '@deepseek-ai/dsh-session-title': specifier: workspace:^ - version: link:../../packages/session-title/session-title + version: link:../../packages/session/session-title '@deepseek-ai/dsh-settings': specifier: workspace:^ version: link:../../packages/settings/settings @@ -7280,13 +7280,13 @@ importers: version: link:../../packages/util/timeout '@deepseek-ai/dsh-timeout-policy': specifier: workspace:^ - version: link:../../packages/timeout/timeout-policy + version: link:../../packages/guard/timeout-policy '@deepseek-ai/dsh-token-meter': specifier: workspace:^ version: link:../../packages/llm/token-meter '@deepseek-ai/dsh-tool-ask-user': specifier: workspace:^ - version: link:../../packages/ui/tool-ask-user + version: link:../../packages/interaction/tool-ask-user '@deepseek-ai/dsh-tool-bash': specifier: workspace:^ version: link:../../packages/bash/tool-bash @@ -7295,7 +7295,7 @@ importers: version: link:../../packages/pty/tool-bash-persistent '@deepseek-ai/dsh-tool-cordis': specifier: workspace:^ - version: link:../../packages/cordis/tool-cordis + version: link:../../packages/self-modification/tool-cordis '@deepseek-ai/dsh-tool-fs': specifier: workspace:^ version: link:../../packages/fs/tool-fs @@ -7334,10 +7334,10 @@ importers: version: link:../../packages/typert/type-meta '@deepseek-ai/dsh-user-approval': specifier: workspace:^ - version: link:../../packages/ui/user-approval + version: link:../../packages/interaction/user-approval '@deepseek-ai/dsh-user-interaction': specifier: workspace:^ - version: link:../../packages/ui/user-interaction + version: link:../../packages/interaction/user-interaction '@deepseek-ai/dsh-web': specifier: workspace:^ version: link:../../packages/web/web @@ -14103,7 +14103,7 @@ snapshots: '@opentelemetry/api-logs@0.220.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api@1.9.0': {} diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json index ec8c282b80..40b3f620e3 100644 --- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json +++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json @@ -8,11 +8,11 @@ }, { "role": "user", - "content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source coding agent built on the DeepSeek Harness SDK.\n\nIt uses an architecture where **everything is a plugin**.\n\n## Internal testing notice\n\nDeepSeek Harness is under internal testing. Features and interfaces may change.\n\nThe internal build uploads all Session Logs by default to help diagnose reported problems. Set `DSH_TELEMETRY_DISABLED=1` to disable telemetry. Send feedback through the internal WeChat group.\n\n## Install\n\nClone the repository, then run the installer:\n\n```sh\ngit clone \ncd deepseek-harness\nscripts/install.sh\n```\n\nThe installer requires `git` and Node `^22.19 || >=24`, offers to install `pnpm` when it is missing, prompts for a DeepSeek API key, builds the required repository artifacts, and launches the Web UI.\n\nThe default active checkout is `~/.dsh/source/current`, and the launcher is linked into `~/.local/bin`. Re-run the installer to update. [`scripts/install.sh`](scripts/install.sh) owns alternate locations, update mechanics, and recovery options.\n\n## Use DeepSeek Harness\n\n### Web UI\n\nFor the recommended local interface, choose Web UI when the installer finishes. To start it later, or after updating the active checkout, build the repository and run:\n\n```sh\n(cd ~/.dsh/source/current && pnpm run build)\ndsh web\n```\n\nThe path above is the installer's default. If you set `DSH_SOURCE` or `DSH_CURRENT`, or reused an existing checkout, replace `~/.dsh/source/current` with that checkout path; see [`scripts/install.sh`](scripts/install.sh) for details. The Web UI is served at `http://127.0.0.1:3080` by default.\n\n### Profiles\n\n`dsh` boots profiles — ordered stacks of plugin-bundle patch layers under your own overrides in `$DSH_HOME/profiles/`:\n\n```sh\ndsh --profile web # the browser UI (same as: dsh web)\ndsh plugin --profile tui add # install a plugin into a custom profile\ndsh --profile tui # boot it\n```\n\nThe [CLI contract](apps/cli/README.md#profiles) describes profile layout, layer semantics, and config dump commands.\n\n### Headless\n\nRun one task, print the final answer, and exit:\n\n```sh\ndsh run \"summarize this workspace\"\n```\n\n### Automation and SDKs\n\nFrom a source checkout with `DEEPSEEK_API_KEY` in the environment or its root `.env`, start the ACP automation server:\n\n```sh\npnpm run demo:acp\n```\n\nThe [Python SDK](python/README.md) drives a bundled JSON-RPC runtime. The [examples](examples/README.md) cover the runnable headless, ACP, JSON-RPC, Code Mode, and self-referential compositions.\n\n## Why DeepSeek Harness\n\nBuilt-in capabilities cover file reading, editing, and search; shell and persistent PTY execution; reusable skills; task tracking, goals, plans, todos, and background tasks; subagents and workflows; sandboxing and approvals; settings and credentials; persistent, resumable, forkable, and queryable sessions; LSP and web access; context compaction; and telemetry. Each composition selects the subset appropriate to its surface. The Web UI includes Plan Mode.\n\n- **Everything is a plugin.** Models, tools, policies, storage, context management, and interfaces are composable [Cordis plugins](docs/user/develop/basic/index.md), so deployments can extend or replace behavior without forking the agent loop. See the [architecture](docs/architecture.md) for the underlying design.\n- **Runs are reconstructable.** Anything visible to the model is logged in the authoritative session stream; persistence, resume/fork/query, replay, telemetry, and UIs derive from the same events. See the [session-log architecture](docs/architecture.md#session-log).\n- **Code Mode (opt-in).** It exposes a `run_code` tool and a generated TypeScript SDK; only program output re-enters model context. See [Code Mode](packages/core/tools/README.md#code-mode).\n- **Self-referential Cordis tools are opt-in.** They let the agent inspect its live runtime and mount or unmount plugins while it runs. See the [Cordis tools](packages/cordis/tool-cordis/README.md).\n\n## Community\n\nFollow DeepSeek Harness on Twitter for project updates.\n\n## Development\n\nStart with the [development guide](docs/development.md) and read the [architecture](docs/architecture.md) before changing packages.\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\nDeepSeek Harness is currently in internal testing.\n\n## License\n\n[BSD 3-Clause](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n" + "content": "# DeepSeek Harness\n\nEnglish | [中文](README.zh.md)\n\nDeepSeek Harness (`dsh`) is an open-source coding agent built on the DeepSeek Harness SDK.\n\nIt uses an architecture where **everything is a plugin**.\n\n## Internal testing notice\n\nDeepSeek Harness is under internal testing. Features and interfaces may change.\n\nThe internal build uploads all Session Logs by default to help diagnose reported problems. Set `DSH_TELEMETRY_DISABLED=1` to disable telemetry. Send feedback through the internal WeChat group.\n\n## Install\n\nClone the repository, then run the installer:\n\n```sh\ngit clone \ncd deepseek-harness\nscripts/install.sh\n```\n\nThe installer requires `git` and Node `^22.19 || >=24`, offers to install `pnpm` when it is missing, prompts for a DeepSeek API key, builds the required repository artifacts, and launches the Web UI.\n\nThe default active checkout is `~/.dsh/source/current`, and the launcher is linked into `~/.local/bin`. Re-run the installer to update. [`scripts/install.sh`](scripts/install.sh) owns alternate locations, update mechanics, and recovery options.\n\n## Use DeepSeek Harness\n\n### Web UI\n\nFor the recommended local interface, choose Web UI when the installer finishes. To start it later, or after updating the active checkout, build the repository and run:\n\n```sh\n(cd ~/.dsh/source/current && pnpm run build)\ndsh web\n```\n\nThe path above is the installer's default. If you set `DSH_SOURCE` or `DSH_CURRENT`, or reused an existing checkout, replace `~/.dsh/source/current` with that checkout path; see [`scripts/install.sh`](scripts/install.sh) for details. The Web UI is served at `http://127.0.0.1:3080` by default.\n\n### Profiles\n\n`dsh` boots profiles — ordered stacks of plugin-bundle patch layers under your own overrides in `$DSH_HOME/profiles/`:\n\n```sh\ndsh --profile web # the browser UI (same as: dsh web)\ndsh plugin --profile tui add # install a plugin into a custom profile\ndsh --profile tui # boot it\n```\n\nThe [CLI contract](apps/cli/README.md#profiles) describes profile layout, layer semantics, and config dump commands.\n\n### Headless\n\nRun one task, print the final answer, and exit:\n\n```sh\ndsh run \"summarize this workspace\"\n```\n\n### Automation and SDKs\n\nFrom a source checkout with `DEEPSEEK_API_KEY` in the environment or its root `.env`, start the ACP automation server:\n\n```sh\npnpm run demo:acp\n```\n\nThe [Python SDK](python/README.md) drives a bundled JSON-RPC runtime. The [examples](examples/README.md) cover the runnable headless, ACP, JSON-RPC, Code Mode, and self-referential compositions.\n\n## Why DeepSeek Harness\n\nBuilt-in capabilities cover file reading, editing, and search; shell and persistent PTY execution; reusable skills; task tracking, goals, plans, todos, and background tasks; subagents and workflows; sandboxing and approvals; settings and credentials; persistent, resumable, forkable, and queryable sessions; LSP and web access; context compaction; and telemetry. Each composition selects the subset appropriate to its surface. The Web UI includes Plan Mode.\n\n- **Everything is a plugin.** Models, tools, policies, storage, context management, and interfaces are composable [Cordis plugins](docs/user/develop/basic/index.md), so deployments can extend or replace behavior without forking the agent loop. See the [architecture](docs/architecture.md) for the underlying design.\n- **Runs are reconstructable.** Anything visible to the model is logged in the authoritative session stream; persistence, resume/fork/query, replay, telemetry, and UIs derive from the same events. See the [session-log architecture](docs/architecture.md#session-log).\n- **Code Mode (opt-in).** It exposes a `run_code` tool and a generated TypeScript SDK; only program output re-enters model context. See [Code Mode](packages/core/tools/README.md#code-mode).\n- **Self-referential Cordis tools are opt-in.** They let the agent inspect its live runtime and mount or unmount plugins while it runs. See the [Cordis tools](packages/self-modification/tool-cordis/README.md).\n\n## Community\n\nFollow DeepSeek Harness on Twitter for project updates.\n\n## Development\n\nStart with the [development guide](docs/development.md) and read the [architecture](docs/architecture.md) before changing packages.\n\nFor agents, follow [AGENTS.md](AGENTS.md).\n\nDeepSeek Harness is currently in internal testing.\n\n## License\n\n[BSD 3-Clause](LICENSE)\n\nThird-party dependencies and their licenses are disclosed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).\n" }, { "role": "assistant", - "content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(编程智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测声明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\n为帮助诊断上报的问题,内测版本默认上传所有会话日志。设置 `DSH_TELEMETRY_DISABLED=1` 可关闭遥测。请通过内部企业微信群反馈问题和建议。\n\n## 安装\n\n克隆仓库,然后运行安装器:\n\n```sh\ngit clone \ncd deepseek-harness\nscripts/install.sh\n```\n\n安装器要求系统已安装 `git` 和 Node `^22.19 || >=24`,缺少 `pnpm` 时可代为安装,并会提示输入 DeepSeek API 密钥,然后构建所需的仓库产物并启动 Web UI。\n\n默认生效的检出位于 `~/.dsh/source/current`,启动器链接到 `~/.local/bin`。再次运行安装器即可更新。其他位置、更新机制和恢复选项由 [`scripts/install.sh`](scripts/install.sh) 负责。\n\n## 使用 DeepSeek Harness\n\n### Web UI\n\n推荐在本地使用 Web UI;安装结束时,选择 Web UI 即可。以后需要启动时,或更新当前生效的检出后,请构建仓库并运行:\n\n```sh\n(cd ~/.dsh/source/current && pnpm run build)\ndsh web\n```\n\n上述路径是安装器的默认位置。如果你设置过 `DSH_SOURCE` 或 `DSH_CURRENT`,或者复用了已有检出,请把 `~/.dsh/source/current` 换成该检出路径;详情见 [`scripts/install.sh`](scripts/install.sh)。Web UI 默认通过 `http://127.0.0.1:3080` 提供服务。\n\n### Profile\n\n`dsh` 启动 profile:按序叠放的插件组合包 patch 层,之上再叠加你在 `$DSH_HOME/profiles/` 中的自有覆盖层:\n\n```sh\ndsh --profile web # the browser UI (same as: dsh web)\ndsh plugin --profile tui add # install a plugin into a custom profile\ndsh --profile tui # boot it\n```\n\nprofile 布局、层语义与配置输出命令详见 [CLI(命令行界面)契约](apps/cli/README.md#profiles)。\n\n### Headless\n\n运行一项任务,打印最终答案后退出:\n\n```sh\ndsh run \"summarize this workspace\"\n```\n\n### 自动化与 SDK\n\n在源码检出中通过环境变量或根目录 `.env` 设置 `DEEPSEEK_API_KEY`,然后启动 ACP(Agent Client Protocol)自动化服务器:\n\n```sh\npnpm run demo:acp\n```\n\n[Python SDK](python/README.md) 驱动随附的 JSON-RPC 运行时。[示例](examples/README.md)涵盖可运行的 headless、ACP、JSON-RPC、Code Mode 和自指组合。\n\n## 为什么选择 DeepSeek Harness\n\n内置功能涵盖文件读取、编辑与搜索、shell 和持久 PTY 执行、可复用 skill(技能)、任务跟踪、目标、计划、待办事项与后台任务、subagent 与工作流、沙箱与审批、设置与凭据、可持久化、恢复、fork 与查询的会话、LSP 与 Web 访问、上下文压缩(context compaction),以及遥测。每个组合只选用适合其使用方式的能力子集。Web UI 包含 Plan Mode。\n\n- **一切皆插件。** 模型、工具、策略、存储、上下文管理和界面均可组合为 [Cordis 插件](docs/user/develop/basic/index.md),部署方无需 fork agent loop(智能体循环)即可扩展或替换行为。底层设计见[架构文档](docs/architecture.md)。\n- **运行可重建。** 凡是模型可见的内容,都会记录在权威会话流中;持久化、恢复/fork/查询、回放、遥测和 UI 均从同一组事件派生。参见[会话日志架构](docs/architecture.md#session-log)。\n- **Code Mode(需显式启用)。** 它会提供 `run_code` 工具和生成的 TypeScript SDK,只有程序输出会重新进入模型上下文。参见 [Code Mode](packages/core/tools/README.md#code-mode)。\n- **自指 Cordis 工具需显式启用。** 这些工具可让 agent 检查自身的实时运行时,并在运行中挂载或卸载插件。参见 [Cordis 工具](packages/cordis/tool-cordis/README.md)。\n\n## 社区\n\n扫描二维码,或打开 DeepSeek Harness 微信社区申请页面 申请加入。\n\n

\n \"DeepSeek\n

\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\nDeepSeek Harness 目前处于内测阶段。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证在 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) 中披露。\n" + "content": "# DeepSeek Harness\n\n[English](README.md) | 中文\n\nDeepSeek Harness(`dsh`)是一款基于 DeepSeek Harness SDK 构建的开源 coding agent(编程智能体)。\n\n它采用了**一切皆插件**的架构。\n\n## 内测声明\n\nDeepSeek Harness 正处于内部测试阶段,功能和接口可能发生变化。\n\n为帮助诊断上报的问题,内测版本默认上传所有会话日志。设置 `DSH_TELEMETRY_DISABLED=1` 可关闭遥测。请通过内部企业微信群反馈问题和建议。\n\n## 安装\n\n克隆仓库,然后运行安装器:\n\n```sh\ngit clone \ncd deepseek-harness\nscripts/install.sh\n```\n\n安装器要求系统已安装 `git` 和 Node `^22.19 || >=24`,缺少 `pnpm` 时可代为安装,并会提示输入 DeepSeek API 密钥,然后构建所需的仓库产物并启动 Web UI。\n\n默认生效的检出位于 `~/.dsh/source/current`,启动器链接到 `~/.local/bin`。再次运行安装器即可更新。其他位置、更新机制和恢复选项由 [`scripts/install.sh`](scripts/install.sh) 负责。\n\n## 使用 DeepSeek Harness\n\n### Web UI\n\n推荐在本地使用 Web UI;安装结束时,选择 Web UI 即可。以后需要启动时,或更新当前生效的检出后,请构建仓库并运行:\n\n```sh\n(cd ~/.dsh/source/current && pnpm run build)\ndsh web\n```\n\n上述路径是安装器的默认位置。如果你设置过 `DSH_SOURCE` 或 `DSH_CURRENT`,或者复用了已有检出,请把 `~/.dsh/source/current` 换成该检出路径;详情见 [`scripts/install.sh`](scripts/install.sh)。Web UI 默认通过 `http://127.0.0.1:3080` 提供服务。\n\n### Profile\n\n`dsh` 启动 profile:按序叠放的插件组合包 patch 层,之上再叠加你在 `$DSH_HOME/profiles/` 中的自有覆盖层:\n\n```sh\ndsh --profile web # the browser UI (same as: dsh web)\ndsh plugin --profile tui add # install a plugin into a custom profile\ndsh --profile tui # boot it\n```\n\nprofile 布局、层语义与配置输出命令详见 [CLI(命令行界面)契约](apps/cli/README.md#profiles)。\n\n### Headless\n\n运行一项任务,打印最终答案后退出:\n\n```sh\ndsh run \"summarize this workspace\"\n```\n\n### 自动化与 SDK\n\n在源码检出中通过环境变量或根目录 `.env` 设置 `DEEPSEEK_API_KEY`,然后启动 ACP(Agent Client Protocol)自动化服务器:\n\n```sh\npnpm run demo:acp\n```\n\n[Python SDK](python/README.md) 驱动随附的 JSON-RPC 运行时。[示例](examples/README.md)涵盖可运行的 headless、ACP、JSON-RPC、Code Mode 和自指组合。\n\n## 为什么选择 DeepSeek Harness\n\n内置功能涵盖文件读取、编辑与搜索、shell 和持久 PTY 执行、可复用 skill(技能)、任务跟踪、目标、计划、待办事项与后台任务、subagent 与工作流、沙箱与审批、设置与凭据、可持久化、恢复、fork 与查询的会话、LSP 与 Web 访问、上下文压缩(context compaction),以及遥测。每个组合只选用适合其使用方式的能力子集。Web UI 包含 Plan Mode。\n\n- **一切皆插件。** 模型、工具、策略、存储、上下文管理和界面均可组合为 [Cordis 插件](docs/user/develop/basic/index.md),部署方无需 fork agent loop(智能体循环)即可扩展或替换行为。底层设计见[架构文档](docs/architecture.md)。\n- **运行可重建。** 凡是模型可见的内容,都会记录在权威会话流中;持久化、恢复/fork/查询、回放、遥测和 UI 均从同一组事件派生。参见[会话日志架构](docs/architecture.md#session-log)。\n- **Code Mode(需显式启用)。** 它会提供 `run_code` 工具和生成的 TypeScript SDK,只有程序输出会重新进入模型上下文。参见 [Code Mode](packages/core/tools/README.md#code-mode)。\n- **自指 Cordis 工具需显式启用。** 这些工具可让 agent 检查自身的实时运行时,并在运行中挂载或卸载插件。参见 [Cordis 工具](packages/self-modification/tool-cordis/README.md)。\n\n## 社区\n\n扫描二维码,或打开 DeepSeek Harness 微信社区申请页面 申请加入。\n\n

\n \"DeepSeek\n

\n\n## 开发\n\n请先阅读[开发指南](docs/development.md);修改包之前,请阅读[架构文档](docs/architecture.md)。\n\n面向 agent:遵循 [AGENTS.md](AGENTS.md)。\n\nDeepSeek Harness 目前处于内测阶段。\n\n## 许可证\n\n[BSD 3-Clause](LICENSE)\n\n第三方依赖及其许可证在 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) 中披露。\n" }, { "role": "user", diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 903172e48f..9180a3bd9a 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -443,7 +443,7 @@ { "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionInspection", - "source": "packages/session-persistence/session-persistence/src/index.ts" + "source": "packages/session/session-persistence/src/index.ts" }, { "doc": "docs/core-data-structures/persistence.md", diff --git a/scripts/verify-package-paths.ts b/scripts/verify-package-paths.ts index f709354899..85c18e3cee 100644 --- a/scripts/verify-package-paths.ts +++ b/scripts/verify-package-paths.ts @@ -1,8 +1,8 @@ /** * Find stale root-relative `packages/...` references in repo-authored prose and - * TypeScript. A missing path is reported only when it names a real package leaf; - * globs, placeholders, hypothetical packages, and unbuilt `lib/` output are - * outside the check. + * TypeScript. A missing path is reported only when it names a real package leaf + * outside its own explaining group directory; globs, placeholders, hypothetical + * packages, and unbuilt `lib/` output are outside the check. */ import { existsSync, globSync } from 'node:fs' @@ -66,7 +66,15 @@ function isDriftedPackageReference(ref: string): boolean { const libAt = parts.indexOf('lib') if (libAt === 3 && existsSync(resolve(root, parts.slice(0, 3).join('/')))) return false // A missing reference is drift only when a path segment names a live package. - return ref.split('/').slice(1).some(segment => packageNames.has(segment)) + // A leading segment that is itself an existing group directory is explained by + // the group, not by a relocated leaf sharing its name (`client` is both the + // client-modules group and the scaffold leaf), so only later segments count. + const segments = ref.split('/').slice(1) + const [group] = segments + const scanned = group !== undefined && segments.length > 1 && existsSync(resolve(root, 'packages', group)) + ? segments.slice(1) + : segments + return scanned.some(segment => packageNames.has(segment)) } /** Find missing package references whose path names a live package; bare paths, typos, and illustrative skeletons do not count. */ From ba3125234a4e968d19220eeac10642c41524fc19 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:06:07 +0800 Subject: [PATCH 06/19] docs: rename core-data-structures/ to subsystems/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The folder is becoming the home of one-doc-per-subsystem pages (intro + data structures + cordis services/events), so the name must describe the whole contract, not just the type-vocabulary third of it. Mechanical rename rebuilt on current master: every inbound Markdown link, generator constant, website route, type-equiv manifest path, and spec expectation moves together; the zh sides of the notes whose prose names the folder are aligned (子系统) in the same change; touched bilingual pairs re-recorded; translation-prompt snapshot re-recorded (its example embeds development.md). Historical Agent Note slugs keep their dated filenames. --- ...eneric-long-running-tool-runtime.i18n.yaml | 4 +- ...06-20-generic-long-running-tool-runtime.md | 2 +- ...20-generic-long-running-tool-runtime.zh.md | 2 +- ...andatory-app-attribution-headers.i18n.yaml | 4 +- ...06-21-mandatory-app-attribution-headers.md | 2 +- ...21-mandatory-app-attribution-headers.zh.md | 2 +- ...stdin-env-trusted-plugin-surface.i18n.yaml | 4 +- ...0-bash-stdin-env-trusted-plugin-surface.md | 2 +- ...ash-stdin-env-trusted-plugin-surface.zh.md | 2 +- ...2026-07-15-agent-initiator-scope.i18n.yaml | 4 +- .../2026-07-15-agent-initiator-scope.md | 2 +- .../2026-07-15-agent-initiator-scope.zh.md | 2 +- .../2026-06-30-interception-seams.i18n.yaml | 4 +- .../feature/2026-06-30-interception-seams.md | 2 +- .../2026-06-30-interception-seams.zh.md | 2 +- .../2026-07-05-dynamic-workflows.i18n.yaml | 4 +- .../feature/2026-07-05-dynamic-workflows.md | 2 +- .../2026-07-05-dynamic-workflows.zh.md | 2 +- .../feature/2026-07-05-skill-system.i18n.yaml | 4 +- .../feature/2026-07-05-skill-system.md | 2 +- .../feature/2026-07-05-skill-system.zh.md | 2 +- .../feature/2026-07-06-sandbox.i18n.yaml | 4 +- .../implemented/feature/2026-07-06-sandbox.md | 2 +- .../feature/2026-07-06-sandbox.zh.md | 2 +- ...-10-parallel-tool-call-execution.i18n.yaml | 4 +- ...2026-07-10-parallel-tool-call-execution.md | 2 +- ...6-07-10-parallel-tool-call-execution.zh.md | 2 +- ...7-31-code-mode-language-dispatch.i18n.yaml | 4 +- .../2026-07-31-code-mode-language-dispatch.md | 4 +- ...26-07-31-code-mode-language-dispatch.zh.md | 4 +- ...-20-core-data-structures-catalog.i18n.yaml | 4 +- ...2026-06-20-core-data-structures-catalog.md | 4 +- ...6-06-20-core-data-structures-catalog.zh.md | 2 +- ...-fork-child-replay-seed-boundary.i18n.yaml | 4 +- ...6-06-22-fork-child-replay-seed-boundary.md | 2 +- ...6-22-fork-child-replay-seed-boundary.zh.md | 2 +- .agents/skills/dsh-code-review/SKILL.md | 2 +- docs/AGENTS.md | 4 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 8 +- docs/architecture.zh.md | 8 +- docs/config-catalog.md | 12 +- docs/cordis-catalog/events.md | 84 +-- docs/cordis-catalog/services.md | 78 +-- docs/development.i18n.yaml | 4 +- docs/development.md | 4 +- docs/development.zh.md | 4 +- docs/graph-atlas.md | 2 +- docs/i18n/style-samples.md | 4 +- docs/persistence-catalog.md | 26 +- ...ice-misclassified-child-failures.i18n.yaml | 4 +- ...ial-notice-misclassified-child-failures.md | 2 +- ...-notice-misclassified-child-failures.zh.md | 2 +- .../approval.i18n.yaml | 2 +- .../approval.md | 0 .../approval.zh.md | 0 .../bash.i18n.yaml | 0 .../bash.md | 0 .../bash.zh.md | 0 .../code-runtime.i18n.yaml | 0 .../code-runtime.md | 0 .../code-runtime.zh.md | 0 .../commands.i18n.yaml | 2 +- .../commands.md | 0 .../commands.zh.md | 0 .../compaction.i18n.yaml | 2 +- .../compaction.md | 0 .../compaction.zh.md | 0 .../core.i18n.yaml | 2 +- .../core.md | 0 .../core.zh.md | 0 .../credentials.i18n.yaml | 2 +- .../credentials.md | 0 .../credentials.zh.md | 0 .../filesystem.i18n.yaml | 0 .../filesystem.md | 0 .../filesystem.zh.md | 0 .../goal.i18n.yaml | 0 .../goal.md | 0 .../goal.zh.md | 0 .../llm-streaming.i18n.yaml | 2 +- .../llm-streaming.md | 0 .../llm-streaming.zh.md | 0 .../lsp.i18n.yaml | 0 .../lsp.md | 0 .../lsp.zh.md | 0 .../persistence.i18n.yaml | 2 +- .../persistence.md | 0 .../persistence.zh.md | 0 .../pty.i18n.yaml | 0 .../pty.md | 0 .../pty.zh.md | 0 .../sandbox.i18n.yaml | 0 .../sandbox.md | 0 .../sandbox.zh.md | 0 .../scope.i18n.yaml | 0 .../scope.md | 0 .../scope.zh.md | 0 .../session-query.i18n.yaml | 0 .../session-query.md | 0 .../session-query.zh.md | 0 .../session-reference.i18n.yaml | 2 +- .../session-reference.md | 0 .../session-reference.zh.md | 0 .../session-title.i18n.yaml | 2 +- .../session-title.md | 0 .../session-title.zh.md | 0 .../session.i18n.yaml | 2 +- .../session.md | 0 .../session.zh.md | 0 .../settings.i18n.yaml | 2 +- .../settings.md | 0 .../settings.zh.md | 0 .../skills.i18n.yaml | 2 +- .../skills.md | 0 .../skills.zh.md | 0 .../spill.i18n.yaml | 0 .../spill.md | 0 .../spill.zh.md | 0 .../subagent.i18n.yaml | 0 .../subagent.md | 0 .../subagent.zh.md | 0 .../subprocess.i18n.yaml | 0 .../subprocess.md | 0 .../subprocess.zh.md | 0 .../system-prompt.i18n.yaml | 2 +- .../system-prompt.md | 0 .../system-prompt.zh.md | 0 .../tasks.i18n.yaml | 0 .../tasks.md | 0 .../tasks.zh.md | 0 .../token-meter.i18n.yaml | 0 .../token-meter.md | 0 .../token-meter.zh.md | 0 .../tools.i18n.yaml | 2 +- .../tools.md | 0 .../tools.zh.md | 0 .../typert.i18n.yaml | 0 .../typert.md | 0 .../typert.zh.md | 0 .../user-interaction.i18n.yaml | 2 +- .../user-interaction.md | 0 .../user-interaction.zh.md | 0 .../web.i18n.yaml | 0 .../web.md | 0 .../web.zh.md | 0 .../workflow.i18n.yaml | 0 .../workflow.md | 0 .../workflow.zh.md | 0 docs/tool-catalog.md | 2 +- packages/bash/bash/README.i18n.yaml | 4 +- packages/bash/bash/README.md | 2 +- packages/bash/bash/README.zh.md | 2 +- packages/compact/compact/README.i18n.yaml | 4 +- packages/compact/compact/README.md | 2 +- packages/compact/compact/README.zh.md | 2 +- packages/core/session/README.i18n.yaml | 4 +- packages/core/session/README.md | 2 +- packages/core/session/README.zh.md | 2 +- packages/core/tools/src/index.ts | 2 +- packages/goal/goal/README.i18n.yaml | 4 +- packages/goal/goal/README.md | 2 +- packages/goal/goal/README.zh.md | 2 +- packages/sandbox/sandbox/README.i18n.yaml | 4 +- packages/sandbox/sandbox/README.md | 2 +- packages/sandbox/sandbox/README.zh.md | 2 +- .../session/session-title/README.i18n.yaml | 6 +- packages/session/session-title/README.md | 2 +- packages/session/session-title/README.zh.md | 2 +- .../subprocess/subprocess/README.i18n.yaml | 4 +- packages/subprocess/subprocess/README.md | 2 +- packages/subprocess/subprocess/README.zh.md | 2 +- packages/tasks/tasks/README.i18n.yaml | 4 +- packages/tasks/tasks/README.md | 2 +- packages/tasks/tasks/README.zh.md | 2 +- .../typert/generator/src/cordis-catalog.ts | 6 +- .../tests/cordis-catalog-contract.spec.ts | 2 +- scripts/gen-config-catalog.ts | 6 +- scripts/gen-cordis-catalog.ts | 2 +- scripts/gen-doc-graphs.ts | 2 +- scripts/gen-persistence-catalog.ts | 8 +- scripts/gen-tool-catalog.ts | 2 +- scripts/project-doc-site.spec.ts | 8 +- .../request-response.expected.json | 4 +- scripts/type-equiv.manifest.json | 624 +++++++++--------- website/docs.ts | 10 +- 186 files changed, 564 insertions(+), 564 deletions(-) rename docs/{core-data-structures => subsystems}/approval.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/approval.md (100%) rename docs/{core-data-structures => subsystems}/approval.zh.md (100%) rename docs/{core-data-structures => subsystems}/bash.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/bash.md (100%) rename docs/{core-data-structures => subsystems}/bash.zh.md (100%) rename docs/{core-data-structures => subsystems}/code-runtime.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/code-runtime.md (100%) rename docs/{core-data-structures => subsystems}/code-runtime.zh.md (100%) rename docs/{core-data-structures => subsystems}/commands.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/commands.md (100%) rename docs/{core-data-structures => subsystems}/commands.zh.md (100%) rename docs/{core-data-structures => subsystems}/compaction.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/compaction.md (100%) rename docs/{core-data-structures => subsystems}/compaction.zh.md (100%) rename docs/{core-data-structures => subsystems}/core.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/core.md (100%) rename docs/{core-data-structures => subsystems}/core.zh.md (100%) rename docs/{core-data-structures => subsystems}/credentials.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/credentials.md (100%) rename docs/{core-data-structures => subsystems}/credentials.zh.md (100%) rename docs/{core-data-structures => subsystems}/filesystem.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/filesystem.md (100%) rename docs/{core-data-structures => subsystems}/filesystem.zh.md (100%) rename docs/{core-data-structures => subsystems}/goal.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/goal.md (100%) rename docs/{core-data-structures => subsystems}/goal.zh.md (100%) rename docs/{core-data-structures => subsystems}/llm-streaming.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/llm-streaming.md (100%) rename docs/{core-data-structures => subsystems}/llm-streaming.zh.md (100%) rename docs/{core-data-structures => subsystems}/lsp.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/lsp.md (100%) rename docs/{core-data-structures => subsystems}/lsp.zh.md (100%) rename docs/{core-data-structures => subsystems}/persistence.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/persistence.md (100%) rename docs/{core-data-structures => subsystems}/persistence.zh.md (100%) rename docs/{core-data-structures => subsystems}/pty.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/pty.md (100%) rename docs/{core-data-structures => subsystems}/pty.zh.md (100%) rename docs/{core-data-structures => subsystems}/sandbox.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/sandbox.md (100%) rename docs/{core-data-structures => subsystems}/sandbox.zh.md (100%) rename docs/{core-data-structures => subsystems}/scope.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/scope.md (100%) rename docs/{core-data-structures => subsystems}/scope.zh.md (100%) rename docs/{core-data-structures => subsystems}/session-query.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/session-query.md (100%) rename docs/{core-data-structures => subsystems}/session-query.zh.md (100%) rename docs/{core-data-structures => subsystems}/session-reference.i18n.yaml (79%) rename docs/{core-data-structures => subsystems}/session-reference.md (100%) rename docs/{core-data-structures => subsystems}/session-reference.zh.md (100%) rename docs/{core-data-structures => subsystems}/session-title.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/session-title.md (100%) rename docs/{core-data-structures => subsystems}/session-title.zh.md (100%) rename docs/{core-data-structures => subsystems}/session.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/session.md (100%) rename docs/{core-data-structures => subsystems}/session.zh.md (100%) rename docs/{core-data-structures => subsystems}/settings.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/settings.md (100%) rename docs/{core-data-structures => subsystems}/settings.zh.md (100%) rename docs/{core-data-structures => subsystems}/skills.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/skills.md (100%) rename docs/{core-data-structures => subsystems}/skills.zh.md (100%) rename docs/{core-data-structures => subsystems}/spill.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/spill.md (100%) rename docs/{core-data-structures => subsystems}/spill.zh.md (100%) rename docs/{core-data-structures => subsystems}/subagent.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/subagent.md (100%) rename docs/{core-data-structures => subsystems}/subagent.zh.md (100%) rename docs/{core-data-structures => subsystems}/subprocess.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/subprocess.md (100%) rename docs/{core-data-structures => subsystems}/subprocess.zh.md (100%) rename docs/{core-data-structures => subsystems}/system-prompt.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/system-prompt.md (100%) rename docs/{core-data-structures => subsystems}/system-prompt.zh.md (100%) rename docs/{core-data-structures => subsystems}/tasks.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/tasks.md (100%) rename docs/{core-data-structures => subsystems}/tasks.zh.md (100%) rename docs/{core-data-structures => subsystems}/token-meter.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/token-meter.md (100%) rename docs/{core-data-structures => subsystems}/token-meter.zh.md (100%) rename docs/{core-data-structures => subsystems}/tools.i18n.yaml (80%) rename docs/{core-data-structures => subsystems}/tools.md (100%) rename docs/{core-data-structures => subsystems}/tools.zh.md (100%) rename docs/{core-data-structures => subsystems}/typert.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/typert.md (100%) rename docs/{core-data-structures => subsystems}/typert.zh.md (100%) rename docs/{core-data-structures => subsystems}/user-interaction.i18n.yaml (79%) rename docs/{core-data-structures => subsystems}/user-interaction.md (100%) rename docs/{core-data-structures => subsystems}/user-interaction.zh.md (100%) rename docs/{core-data-structures => subsystems}/web.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/web.md (100%) rename docs/{core-data-structures => subsystems}/web.zh.md (100%) rename docs/{core-data-structures => subsystems}/workflow.i18n.yaml (100%) rename docs/{core-data-structures => subsystems}/workflow.md (100%) rename docs/{core-data-structures => subsystems}/workflow.zh.md (100%) diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml index e1171d5877..cb9ed4275a 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md -2026-06-20-generic-long-running-tool-runtime.md: cb9d9487cd274696ae20dddab8c6888b4cf4b833 -2026-06-20-generic-long-running-tool-runtime.zh.md: fe5ca223975445991e7fe96376cbfccf432ad241 +2026-06-20-generic-long-running-tool-runtime.md: 90100ea8f146e505c4fbb95760781839bd4bd34a +2026-06-20-generic-long-running-tool-runtime.zh.md: bdcd8e8fe2a50033177354ce5ce043c4b5264621 diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md index cb9d9487cd..90100ea8f1 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md @@ -23,7 +23,7 @@ Long-running tools are producers. `dsh-tool-bash` adapts a `BashProcess` into in ## Runtime contract -The literal types live in the [task data-structure catalog](../../../../docs/core-data-structures/tasks.md). A producer calls `ctx.tasks.start()` with a kind, label, optional owning `Agent`, optional positive `outputLimitBytes`, and a `run()` function. The runtime completes all failable preflight work before calling `run()` and invokes it once. After `run()` returns hooks, registration commits without another failable step; a producer cannot start work that lacks a collectable task id. +The literal types live in the [task data-structure catalog](../../../../docs/subsystems/tasks.md). A producer calls `ctx.tasks.start()` with a kind, label, optional owning `Agent`, optional positive `outputLimitBytes`, and a `run()` function. The runtime completes all failable preflight work before calling `run()` and invokes it once. After `run()` returns hooks, registration commits without another failable step; a producer cannot start work that lacks a collectable task id. `outputLimitBytes` is producer-owned presentation policy, not a registry buffer. The registry validates and projects it unchanged into `TaskSnapshot`; generic control surfaces apply the cap to complete model-facing output after adding their own status or notice metadata. Omitting it preserves the existing surface behavior, so the runtime does not impose a hidden default on unrelated producer families. diff --git a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md index fe5ca22397..bdcd8e8fe2 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.zh.md @@ -23,7 +23,7 @@ Status: implemented ## 运行时契约 -字面类型见[任务数据结构目录](../../../../docs/core-data-structures/tasks.md)。生产方调用 `ctx.tasks.start()`,传入 kind、label、可选的所属 `Agent`、可选的正数 `outputLimitBytes` 与一个 `run()` 函数。运行时会在调用 `run()` 前完成所有可能失败的预检工作,并且只调用一次。`run()` 返回钩子后,注册过程不会再执行可能失败的步骤而直接提交;生产方无法启动没有可收集 task id 的工作。 +字面类型见[任务数据结构目录](../../../../docs/subsystems/tasks.md)。生产方调用 `ctx.tasks.start()`,传入 kind、label、可选的所属 `Agent`、可选的正数 `outputLimitBytes` 与一个 `run()` 函数。运行时会在调用 `run()` 前完成所有可能失败的预检工作,并且只调用一次。`run()` 返回钩子后,注册过程不会再执行可能失败的步骤而直接提交;生产方无法启动没有可收集 task id 的工作。 `outputLimitBytes` 是生产方拥有的呈现策略,而非注册表缓冲区。注册表校验该值,并将其原样投影到 `TaskSnapshot`;通用控制接口添加自身的状态或通知元数据后,再将该上限应用于完整的面向模型输出。省略该值时保持现有接口行为,因此运行时不会向无关的生产方类别施加隐式默认值。 diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml index b6788a47ef..3db3ea7fa4 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md -2026-06-21-mandatory-app-attribution-headers.md: ad9d65805c8f0c96bd811b5036310d019760627e -2026-06-21-mandatory-app-attribution-headers.zh.md: 3021c7fcca00f2e929d997625c303f9a27dbf673 +2026-06-21-mandatory-app-attribution-headers.md: de9125bc891cc62798480e2eccb2c90cb633de3a +2026-06-21-mandatory-app-attribution-headers.zh.md: cdada9cb235d57ad5cac98ebe0572013cac728ef diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md index ad9d65805c..de9125bc89 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md +++ b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md @@ -51,7 +51,7 @@ Endpoint detection is not part of this Agent Note because no endpoint-specific m The landed contract: -- `dsh-llm` documents the mandatory `User-Agent` attribution contract for `LlmAdapter` authors (`LlmAdapter` JSDoc, package README, and the adapter-contract section of `docs/core-data-structures/llm-streaming.md`). +- `dsh-llm` documents the mandatory `User-Agent` attribution contract for `LlmAdapter` authors (`LlmAdapter` JSDoc, package README, and the adapter-contract section of `docs/subsystems/llm-streaming.md`). - A shared helper (`attributionHeaders` / `userAgent`) constructs the app identity and the standard `User-Agent` value from package metadata, so adapters do not hand-copy version constants. - `dsh-llm-deepseek` sends the shared `User-Agent` on every request and its mock-server suite asserts the exact value. - `dsh-llm-pi-ai` sends the same `User-Agent` through pi-ai's `StreamOptions.headers` hook and its mock-server suite asserts the exact value. diff --git a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md index 3021c7fcca..cdada9cb23 100644 --- a/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.zh.md @@ -51,7 +51,7 @@ LLM(大语言模型)提供方请求应当标识发出请求的产品。这 已落地的契约: -- `dsh-llm` 为 `LlmAdapter` 作者文档化了强制的 `User-Agent` 归属契约(`LlmAdapter` JSDoc、包 README,以及 `docs/core-data-structures/llm-streaming.md` 的适配器契约(adapter contract)章节)。 +- `dsh-llm` 为 `LlmAdapter` 作者文档化了强制的 `User-Agent` 归属契约(`LlmAdapter` JSDoc、包 README,以及 `docs/subsystems/llm-streaming.md` 的适配器契约(adapter contract)章节)。 - 共享辅助函数(`attributionHeaders` / `userAgent`)从包元数据构建应用身份和标准 `User-Agent` 值,适配器无需手动复制版本常量。 - `dsh-llm-deepseek` 在每个请求上发送共享的 `User-Agent`,其 mock 服务器套件断言精确值。 - `dsh-llm-pi-ai` 通过 pi-ai 的 `StreamOptions.headers` 钩子发送相同的 `User-Agent`,其 mock 服务器套件断言精确值。 diff --git a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.i18n.yaml b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.i18n.yaml index 5dbfcf94ef..459ffa188c 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md -2026-06-30-bash-stdin-env-trusted-plugin-surface.md: 556d5dd86dfcc92c4628e68c19390f0033560d25 -2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md: 325e3d303bdb6836c8928fdae00de59fb954ff37 +2026-06-30-bash-stdin-env-trusted-plugin-surface.md: aa0c785be84bcb8a49f8a2670afa0f6bed6277db +2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md: 48108fdd5b8217263590fba336480f1b487e882f diff --git a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md index 556d5dd86d..aa0c785be8 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md +++ b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md @@ -30,4 +30,4 @@ Three deliberate choices: ## Consequences -Hook bridges pass JSON payloads and hook-specific variables through the existing bash seam, retaining its process-group, truncation, and spill behavior. The model surface remains unchanged, and the bash tool remains the sole owner of model-call request construction. The vocabulary lives in [the bash data-structure reference](../../../../docs/core-data-structures/bash.md). +Hook bridges pass JSON payloads and hook-specific variables through the existing bash seam, retaining its process-group, truncation, and spill behavior. The model surface remains unchanged, and the bash tool remains the sole owner of model-call request construction. The vocabulary lives in [the bash data-structure reference](../../../../docs/subsystems/bash.md). diff --git a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md index 325e3d303b..48108fdd5b 100644 --- a/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md +++ b/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.zh.md @@ -30,4 +30,4 @@ Status: implemented ## 后果 -钩子桥接层通过既有的 bash seam 传递 JSON 载荷和钩子特定变量,保留其进程组终止、截断和溢出行为。模型接口面不变,bash 工具仍是模型调用请求构建的唯一所有者。相关词汇定义见 [bash 数据结构参考](../../../../docs/core-data-structures/bash.md)。 +钩子桥接层通过既有的 bash seam 传递 JSON 载荷和钩子特定变量,保留其进程组终止、截断和溢出行为。模型接口面不变,bash 工具仍是模型调用请求构建的唯一所有者。相关词汇定义见 [bash 数据结构参考](../../../../docs/subsystems/bash.md)。 diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml index 233164d4e4..c85bb100aa 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md -2026-07-15-agent-initiator-scope.md: 69648100e76cfc212469854188d664357fec22f1 -2026-07-15-agent-initiator-scope.zh.md: 505d198ccd2a54af1a15fc1ad6c03b27d217eca0 +2026-07-15-agent-initiator-scope.md: 2f388ae1de3dd6583686e129a03f0cd76701e18d +2026-07-15-agent-initiator-scope.zh.md: 64f70a4d1c3368d5621edb7251d0b567505affc0 diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md index 69648100e7..2f388ae1de 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md @@ -12,7 +12,7 @@ Deep process-local infrastructure sometimes needs a trusted initiating Agent bel ## Decision -The mandatory `ctx.agents` service uses Node `AsyncLocalStorage` to carry the initiating Agent. It stores the exact `Agent` directly rather than introducing a one-field frame; a separate private run token records nested boundary lineage only for teardown bookkeeping and carries no identity. The [core-data catalog](../../../../docs/core-data-structures/core.md#initiating-agent) identifies the carried type. +The mandatory `ctx.agents` service uses Node `AsyncLocalStorage` to carry the initiating Agent. It stores the exact `Agent` directly rather than introducing a one-field frame; a separate private run token records nested boundary lineage only for teardown bookkeeping and carries no identity. The [core-data catalog](../../../../docs/subsystems/core.md#initiating-agent) identifies the carried type. `currentInitiator()` reads optionally, `requireInitiator()` throws `no initiating agent is active`, and `withInitiator(agent, operation)` preserves the operation's exact synchronous value or Promise. `withoutInitiator(operation)` establishes a clearing boundary for work that must not inherit an Agent. Session remains derived as `agent.session`; turn, step, tool call, `signal`, model, `cwd`, sandbox, and authorization stay with their existing owners. diff --git a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md index 505d198ccd..64f70a4d1c 100644 --- a/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.zh.md @@ -12,7 +12,7 @@ harness 中存在两种有用但不同的上下文概念。Cordis `Context` 负 ## 决策 -必需的 `ctx.agents` 服务使用 Node `AsyncLocalStorage` 携带发起 Agent。它直接存储同一个 `Agent`,不引入只有一个字段的帧;另一个私有运行标记只记录嵌套边界的谱系,供 teardown 记账使用,不携带身份。[核心数据目录](../../../../docs/core-data-structures/core.md#initiating-agent)标明了所携带的类型。 +必需的 `ctx.agents` 服务使用 Node `AsyncLocalStorage` 携带发起 Agent。它直接存储同一个 `Agent`,不引入只有一个字段的帧;另一个私有运行标记只记录嵌套边界的谱系,供 teardown 记账使用,不携带身份。[核心数据目录](../../../../docs/subsystems/core.md#initiating-agent)标明了所携带的类型。 `currentInitiator()` 用于可选读取,`requireInitiator()` 抛出 `no initiating agent is active`,`withInitiator(agent, operation)` 保留操作返回的同步值或 Promise 本身。`withoutInitiator(operation)` 会建立清空边界,供不得继承 Agent 的工作使用。会话仍通过 `agent.session` 推导;轮次、步骤、工具调用、`signal`、模型、`cwd`、沙箱和授权继续由现有归属方管理。 diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml b/.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml index 3be447fe2f..8481cc20d5 100644 --- a/.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-06-30-interception-seams.md -2026-06-30-interception-seams.md: c318e41cfb1d64230b6151f1febad85d75b1451d -2026-06-30-interception-seams.zh.md: 1b274fae4bc7fde326dbb0eeec54d57f73987803 +2026-06-30-interception-seams.md: 3b2c62cd4413f6d93cef247c51304a8d46753218 +2026-06-30-interception-seams.zh.md: 4e7b6a5ac9ca88919246508cf86c486b06888417 diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-seams.md b/.agents/notes/implemented/feature/2026-06-30-interception-seams.md index c318e41cfb..3b2c62cd44 100644 --- a/.agents/notes/implemented/feature/2026-06-30-interception-seams.md +++ b/.agents/notes/implemented/feature/2026-06-30-interception-seams.md @@ -56,4 +56,4 @@ The seam package does **not** declare `hook/*` session events (the durable hook- ## Consequences -The canonical interception surface is uniformly typed without giving every extension the same power: hooks return decisions, execution wrappers wrap, terminal guards only deny, and final observers only observe. The loop owns session-start, pre-step claim settlement, post-tool context buffering, and stopping; `dsh-tools` owns identity sealing and the five-phase execution pipeline. Their contracts are documented in [architecture.md](../../../../docs/architecture.md), package READMEs, [core interception decisions](../../../../docs/core-data-structures/core.md#interception-decisions), and [tool structures](../../../../docs/core-data-structures/tools.md). The ACP bridge settles an initial pre-step rejection from its blocked no-step turn as `end_turn`, while hook-driven snapshots verify the observable bridge behavior end to end. +The canonical interception surface is uniformly typed without giving every extension the same power: hooks return decisions, execution wrappers wrap, terminal guards only deny, and final observers only observe. The loop owns session-start, pre-step claim settlement, post-tool context buffering, and stopping; `dsh-tools` owns identity sealing and the five-phase execution pipeline. Their contracts are documented in [architecture.md](../../../../docs/architecture.md), package READMEs, [core interception decisions](../../../../docs/subsystems/core.md#interception-decisions), and [tool structures](../../../../docs/subsystems/tools.md). The ACP bridge settles an initial pre-step rejection from its blocked no-step turn as `end_turn`, while hook-driven snapshots verify the observable bridge behavior end to end. diff --git a/.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md b/.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md index 1b274fae4b..4e7b6a5ac9 100644 --- a/.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md +++ b/.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md @@ -56,4 +56,4 @@ seam 包**不**声明 `hook/*` 会话事件(持久的钩子调用日志); ## 后果 -规范拦截表面具有统一的类型化,同时不给每个扩展相同的权力:钩子返回 decision,执行包装层做包装,终结 guard 只能拒绝,最终观测者只能观测。循环负责 session-start、pre-step 领取结算、工具执行后上下文缓冲和 stopping;`dsh-tools` 负责身份封存与五阶段执行流水线。它们的契约记录在 [architecture.md](../../../../docs/architecture.md)、各包 README、[核心拦截 decision](../../../../docs/core-data-structures/core.md#interception-decisions) 与[工具结构](../../../../docs/core-data-structures/tools.md)中。ACP 桥接会把 blocked 无步骤轮次中的首次 pre-step reject 结算为 `end_turn`,而钩子驱动的快照端到端验证可观测的桥接行为。 +规范拦截表面具有统一的类型化,同时不给每个扩展相同的权力:钩子返回 decision,执行包装层做包装,终结 guard 只能拒绝,最终观测者只能观测。循环负责 session-start、pre-step 领取结算、工具执行后上下文缓冲和 stopping;`dsh-tools` 负责身份封存与五阶段执行流水线。它们的契约记录在 [architecture.md](../../../../docs/architecture.md)、各包 README、[核心拦截 decision](../../../../docs/subsystems/core.md#interception-decisions) 与[工具结构](../../../../docs/subsystems/tools.md)中。ACP 桥接会把 blocked 无步骤轮次中的首次 pre-step reject 结算为 `end_turn`,而钩子驱动的快照端到端验证可观测的桥接行为。 diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml index 15054d6ba1..394af669cd 100644 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md -2026-07-05-dynamic-workflows.md: bba62098c66477a3f1929f9029e81c645bfc4d41 -2026-07-05-dynamic-workflows.zh.md: 2005ca14883ae137148541273900c7f3e65769a5 +2026-07-05-dynamic-workflows.md: b0fb2349e64093beab8220ea75715b55903772e3 +2026-07-05-dynamic-workflows.zh.md: 246727649be8acc17dac1335d353e9b1591e3890 diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md index bba62098c6..b0fb2349e6 100644 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md +++ b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md @@ -20,7 +20,7 @@ One deliberate strictness DIVERGENCE from CC: hook misuse — unknown or deferre ### The seam (dsh-workflow) -`ctx.workflows` is an abstract `WorkflowService` in the bash shape — one engine per context, no named-provider registry (engines are deployment swaps, not co-residents). `start(request)` throws synchronously for a script that cannot begin; a returned `WorkflowRun`'s `result` NEVER rejects (failures resolve as `stopReason: 'error' | 'cancelled'`). The `workflow/*` events are observe-only emits carrying DATA SNAPSHOTS (id + meta; `workflow/end` omits the result value), per-listener contained, mirroring `subagent/start`/`subagent/end` — control stays with the run's holder. Vocabulary details: [core-data-structures/workflow.md](../../../../docs/core-data-structures/workflow.md). +`ctx.workflows` is an abstract `WorkflowService` in the bash shape — one engine per context, no named-provider registry (engines are deployment swaps, not co-residents). `start(request)` throws synchronously for a script that cannot begin; a returned `WorkflowRun`'s `result` NEVER rejects (failures resolve as `stopReason: 'error' | 'cancelled'`). The `workflow/*` events are observe-only emits carrying DATA SNAPSHOTS (id + meta; `workflow/end` omits the result value), per-listener contained, mirroring `subagent/start`/`subagent/end` — control stays with the run's holder. Vocabulary details: [subsystems/workflow.md](../../../../docs/subsystems/workflow.md). ### The engine (dsh-workflow-workerthread): one worker thread per run diff --git a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md index 2005ca1488..246727649b 100644 --- a/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.zh.md @@ -20,7 +20,7 @@ harness 可以将一个任务委派给一个子 agent(`dsh-tool-subagent`) ### seam(dsh-workflow) -`ctx.workflows` 是 bash 形态的抽象 `WorkflowService`——每个上下文一个引擎,无命名提供方注册表(引擎是部署级替换,不是共存者)。`start(request)` 对无法启动的脚本同步抛出;返回的 `WorkflowRun` 的 `result` 永不 reject(失败时结算为 `stopReason: 'error' | 'cancelled'`)。`workflow/*` 事件是仅观察的 emit,携带数据快照(id + meta;`workflow/end` 省略 result 值),按监听器隔离,与 `subagent/start`/`subagent/end` 对称——控制权留在 run 的持有者手中。词汇详情见 [core-data-structures/workflow.md](../../../../docs/core-data-structures/workflow.md)。 +`ctx.workflows` 是 bash 形态的抽象 `WorkflowService`——每个上下文一个引擎,无命名提供方注册表(引擎是部署级替换,不是共存者)。`start(request)` 对无法启动的脚本同步抛出;返回的 `WorkflowRun` 的 `result` 永不 reject(失败时结算为 `stopReason: 'error' | 'cancelled'`)。`workflow/*` 事件是仅观察的 emit,携带数据快照(id + meta;`workflow/end` 省略 result 值),按监听器隔离,与 `subagent/start`/`subagent/end` 对称——控制权留在 run 的持有者手中。词汇详情见 [subsystems/workflow.md](../../../../docs/subsystems/workflow.md)。 ### 引擎(dsh-workflow-workerthread):每次运行一个 worker 线程 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml index a98beff699..6fff14bc85 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md -2026-07-05-skill-system.md: a998d70ec934aed4bf7ce32aa711abd47b508a1d -2026-07-05-skill-system.zh.md: 4fa7c4fd657c2f41f16b30679ec95e61a75f8a0c +2026-07-05-skill-system.md: bb7c03a64a3d0d0f700e21bc96633f1c3ea181b9 +2026-07-05-skill-system.zh.md: 8e26e52d3366505a958dd4b19461edf8bf740b00 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.md index a998d70ec9..bb7c03a64a 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.md @@ -28,7 +28,7 @@ Local skill filesystem I/O goes through `ctx.fs` when a filesystem service is lo The registry's `list()` returns every winning summary, while model and user consumers apply the invocation predicates owned by the [independent invocation-policy decision](2026-07-28-skill-invocation-policy.md). The `skill({ name })` tool loads one model-invocable skill for the current agent cwd and returns a tool result containing ``, ``, and ``. `resourceBase` supplies a directory, URL, or opaque provider-managed base for explicitly referenced scripts, references, and assets; resources load only as needed, without directory enumeration. An unresolved name reports that the skill is unknown or no longer available; invalid names and skills with `invocation.modelInvocable: false` retain distinct tool errors. The tool result is the model-visible disclosure path. -The data structures and catalog/tool contract are documented in [skills.md](../../../../docs/core-data-structures/skills.md), with service signatures in the generated [services catalog](../../../../docs/cordis-catalog/services.md). +The data structures and catalog/tool contract are documented in [skills.md](../../../../docs/subsystems/skills.md), with service signatures in the generated [services catalog](../../../../docs/cordis-catalog/services.md). ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md index 4fa7c4fd65..8e26e52d33 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md @@ -28,7 +28,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 注册表的 `list()` 返回全部胜出摘要,而模型与用户消费方应用[独立调用策略决策](2026-07-28-skill-invocation-policy.md)定义的调用判定。`skill({ name })` 工具为当前 agent cwd 加载一个模型可调用的 skill,返回包含 ``、`` 和 `` 的工具结果。`resourceBase` 提供一个目录、URL 或不透明的提供方管理的基路径,用于显式引用的脚本、参考资料和资产;资源仅按需加载,不进行目录枚举。无法解析的名称报告该 skill 未知或不再可用;无效名称和 `invocation.modelInvocable` 为 `false` 的 skill 保留不同的工具错误。工具结果是面向模型的可见披露路径。 -数据结构与目录/工具契约记录在 [skills.md](../../../../docs/core-data-structures/skills.md) 中,服务签名见生成的[服务目录](../../../../docs/cordis-catalog/services.md)。 +数据结构与目录/工具契约记录在 [skills.md](../../../../docs/subsystems/skills.md) 中,服务签名见生成的[服务目录](../../../../docs/cordis-catalog/services.md)。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml index 7294f47357..00f475040c 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-06-sandbox.md -2026-07-06-sandbox.md: 583b388815cd9b2b9cf94ce393839169ce3ffac3 -2026-07-06-sandbox.zh.md: e435b671a42ca5c3ea4f6800bf91d6e006da35d3 +2026-07-06-sandbox.md: 06c5590454a6947030d828f087b92f44207dad6e +2026-07-06-sandbox.zh.md: 8f575e3cd14973aaaaf9254337b985110bc99d6b diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.md index 583b388815..06c5590454 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.md @@ -200,7 +200,7 @@ Costs and accepted limits: In-repo precedents this design copies or contrasts with: - [The capability-seams Agent Note](../architecture/2026-06-13-capability-seams.md) — the interface/implementation/consumer split and the "don't split preemptively" timing rule the second consumer satisfied. -- The `dsh-bash` request/spec split ([the bash vocabulary catalog](../../../../docs/core-data-structures/bash.md)) — the complete `sandboxPolicy` rides its per-call carrier, and the explicit-`resolve()` defaulting convention. +- The `dsh-bash` request/spec split ([the bash vocabulary catalog](../../../../docs/subsystems/bash.md)) — the complete `sandboxPolicy` rides its per-call carrier, and the explicit-`resolve()` defaulting convention. - [The approval seam Agent Note](2026-07-06-approval-seam.md) — the channel escalation asks through; its answerer waterfall, audit pair, and one-package rationale are recorded there. - [Event-sourced sessions](../architecture/2026-06-11-event-sourced-sessions.md) and [standalone log-only events](../simplification/2026-07-28-remove-synthetic-log-only-turns.md) — the log-as-store foundation the per-session modes fold over, and the explicit durability boundary the anchoring design obeys. - [The interception-seams Agent Note](2026-06-30-interception-seams.md) — the `tools/pre-execute` vocabulary the escalation gate deliberately does not reuse (an escalating call has no pre-execute moment of its own). diff --git a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md index e435b671a4..8f575e3cd1 100644 --- a/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md +++ b/.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md @@ -200,7 +200,7 @@ fs/web/todo 在进程内执行,因此它们的沙箱语义是各自 seam 层 本设计复制或对比的仓库内先例: - [能力 seam Agent Note](../architecture/2026-06-13-capability-seams.md)——接口/实现/消费方拆分与「不要过早拆分」的时机规则(第二个消费方满足了该规则)。 -- `dsh-bash` 的 request/spec 拆分([bash 词汇目录](../../../../docs/core-data-structures/bash.md))——完整的 `sandboxPolicy` 搭载其按调用载体,以及显式 `resolve()` 默认约定。 +- `dsh-bash` 的 request/spec 拆分([bash 词汇目录](../../../../docs/subsystems/bash.md))——完整的 `sandboxPolicy` 搭载其按调用载体,以及显式 `resolve()` 默认约定。 - [批准 seam Agent Note](2026-07-06-approval-seam.md)——升级请求通过的通道;其应答器 waterfall(瀑布式事件)、审计对和单包理由记录在那里。 - [事件溯源会话](../architecture/2026-06-11-event-sourced-sessions.md)与[独立纯日志事件](../simplification/2026-07-28-remove-synthetic-log-only-turns.md)——按会话模式 fold 所依赖的日志即存储基础,以及锚定设计遵守的显式持久性边界。 - [拦截 seam Agent Note](2026-06-30-interception-seams.md)——`tools/pre-execute` 词汇,升级门控刻意不复用它(升级调用没有自己的 pre-execute 时刻)。 diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml index 3119d28d71..6462a2c873 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md -2026-07-10-parallel-tool-call-execution.md: 19f5dc189821433052edfa72613980a2e94e2cae -2026-07-10-parallel-tool-call-execution.zh.md: 69bff90c11fa132ded325ef610dabf5c609f21af +2026-07-10-parallel-tool-call-execution.md: 7fd540539fd358b3254470f420a0cc11f2927d45 +2026-07-10-parallel-tool-call-execution.zh.md: 985557974c79d4080c4e35ccbb9740439988261a diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md index 19f5dc1898..7fd540539f 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md @@ -14,7 +14,7 @@ The session log remains authoritative: every started call has an audit event, or ## Decision -Each tool may provide an optional `isConcurrencySafe(args)` classifier. It is synchronous and pure: it examines only the current call's parsed arguments and performs no I/O or mutation. Only an explicit `true` opts in; a missing classifier, invalid arguments, a thrown classifier, or any other return value makes the call exclusive. The canonical type contract lives in the [tool data structures](../../../../docs/core-data-structures/tools.md). +Each tool may provide an optional `isConcurrencySafe(args)` classifier. It is synchronous and pure: it examines only the current call's parsed arguments and performs no I/O or mutation. Only an explicit `true` opts in; a missing classifier, invalid arguments, a thrown classifier, or any other return value makes the call exclusive. The canonical type contract lives in the [tool data structures](../../../../docs/subsystems/tools.md). The classifier is deliberately unary. Returning `true` is the tool's promise that this call may overlap with any sibling call that also returns `true`; the scheduler does not compare calls or prove that their resource accesses are compatible. diff --git a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md index 69bff90c11..985557974c 100644 --- a/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md +++ b/.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.zh.md @@ -14,7 +14,7 @@ Status: implemented ## 决策 -每个工具都可以提供可选的 `isConcurrencySafe(args)` 分类器。该分类器必须是同步纯函数:它只检查当前调用已解析的参数,不执行 I/O 或任何变更。只有显式返回 `true` 才表示选择并行;分类器缺失、参数无效、分类器抛错或返回任何其他值,都会使该调用按独占方式执行。规范类型契约见[工具数据结构](../../../../docs/core-data-structures/tools.md)。 +每个工具都可以提供可选的 `isConcurrencySafe(args)` 分类器。该分类器必须是同步纯函数:它只检查当前调用已解析的参数,不执行 I/O 或任何变更。只有显式返回 `true` 才表示选择并行;分类器缺失、参数无效、分类器抛错或返回任何其他值,都会使该调用按独占方式执行。规范类型契约见[工具数据结构](../../../../docs/subsystems/tools.md)。 分类器有意设计为一元函数。返回 `true` 表示工具承诺:此调用可以与任何同样返回 `true` 的并列调用重叠执行。调度器不会比较调用,也不会证明它们的资源访问相容。 diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml index 66ac99d37c..e6ca4048fd 100644 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md -2026-07-31-code-mode-language-dispatch.md: 96001252d6494d058a8df9974fb5a0d59e7d7112 -2026-07-31-code-mode-language-dispatch.zh.md: aa7eb2a6b4b9117f1d707b37afcdbe12b814bad2 +2026-07-31-code-mode-language-dispatch.md: 71e4fd5a30e90ac66132dbdcea16917bacc80b68 +2026-07-31-code-mode-language-dispatch.zh.md: 1dbb3d889d28669575980d3a667e4f168a5d72db diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md index 96001252d6..71e4fd5a30 100644 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md +++ b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.md @@ -17,7 +17,7 @@ Language selection is a lookup on `ctx.codeRuntime.language`, resolved lazily at - `SDK_RENDERERS` (index.ts) maps a language to its `tools:sdk` renderer — `typescript → renderToolsSdk`, `python → renderToolsSdkPy`. The `tools:sdk` section reads the loaded runtime's language and picks the renderer; `requireCodeRuntime` rejects a `mode: code`/`both` runtime whose language is absent from the table, naming the known languages. - `RUN_CODE_FLAVORS` (code-mode.ts) maps a language to its two model-facing `run_code` strings (tool `description` and the `code` parameter description), so a language's SDK section and its transport schema always agree. -Both tables are read with `Object.hasOwn` before use so a language named `toString`/`constructor` cannot resolve an inherited `Object.prototype` member as a renderer. The two guards differ in reachability: `SDK_RENDERERS`' in-callback guard is unreachable because `requireCodeRuntime` validated the same `const` table earlier in the same callback (it carries a `/* v8 ignore */`), while `RUN_CODE_FLAVORS`' guard is the primary, publicly reachable rejection — any language absent from the flavor table hits it through `run_code`'s language-aware getters, which the public `schemas()` reaches without passing `requireCodeRuntime` first; the test reads one of those getters off the definition directly, under a language absent from both tables. A language present in `SDK_RENDERERS` but not `RUN_CODE_FLAVORS` is drift the shared `CodeSdkLanguage` `satisfies` pins reject at `typecheck`, so it is not an input either guard can see; what the guards still own is a mounted runtime reporting a language absent from both tables. Schema emission reads the runtime through `peekRuntime()` rather than `requireRuntime()`: `undefined` (no runtime mounted, reached by definition readers and `schemas()`, of which the doc-catalog harvest is the only shipped one and none of which feeds a model because assembly passes `requireCodeRuntime` first) degrades to the TypeScript flavor, whereas a mounted unknown language fails loud — this is NOT the silent fallback rejected below, which concerns emitting a wrong-language SDK for a real runtime. Adding a backend language is three parallel edits — a `CodeSdkLanguage` member and the two table entries — plus its renderer and the prose that names the well-known values instead of deriving them (the seam's `dsh-code-runtime` README pair, its `CodeRuntime.language` JSDoc, and the `docs/core-data-structures/code-runtime.md` pair; this package's own README pair and its `Config.mode` JSDoc — no gate checks any of it), with no `agent-loop` or registry-structure change. +Both tables are read with `Object.hasOwn` before use so a language named `toString`/`constructor` cannot resolve an inherited `Object.prototype` member as a renderer. The two guards differ in reachability: `SDK_RENDERERS`' in-callback guard is unreachable because `requireCodeRuntime` validated the same `const` table earlier in the same callback (it carries a `/* v8 ignore */`), while `RUN_CODE_FLAVORS`' guard is the primary, publicly reachable rejection — any language absent from the flavor table hits it through `run_code`'s language-aware getters, which the public `schemas()` reaches without passing `requireCodeRuntime` first; the test reads one of those getters off the definition directly, under a language absent from both tables. A language present in `SDK_RENDERERS` but not `RUN_CODE_FLAVORS` is drift the shared `CodeSdkLanguage` `satisfies` pins reject at `typecheck`, so it is not an input either guard can see; what the guards still own is a mounted runtime reporting a language absent from both tables. Schema emission reads the runtime through `peekRuntime()` rather than `requireRuntime()`: `undefined` (no runtime mounted, reached by definition readers and `schemas()`, of which the doc-catalog harvest is the only shipped one and none of which feeds a model because assembly passes `requireCodeRuntime` first) degrades to the TypeScript flavor, whereas a mounted unknown language fails loud — this is NOT the silent fallback rejected below, which concerns emitting a wrong-language SDK for a real runtime. Adding a backend language is three parallel edits — a `CodeSdkLanguage` member and the two table entries — plus its renderer and the prose that names the well-known values instead of deriving them (the seam's `dsh-code-runtime` README pair, its `CodeRuntime.language` JSDoc, and the `docs/subsystems/code-runtime.md` pair; this package's own README pair and its `Config.mode` JSDoc — no gate checks any of it), with no `agent-loop` or registry-structure change. `code-mode.ts` depends only on the runtime seam (`@deepseek-ai/dsh-code-runtime`), never on a concrete backend; dispatch is by `runtime.language` at run time. The tool layer therefore lands independently of the protocol and backend PRs — it needs only the seam's `language` field, which is already on master. @@ -37,7 +37,7 @@ The standard that cap serves is grammatical validity, and the boundary is delibe ## Consequences -Adding a backend language is three parallel edits — a `CodeSdkLanguage` member, an `SDK_RENDERERS` entry, and a `RUN_CODE_FLAVORS` entry — plus the renderer function the second points at, with no change to `agent-loop` or the registry structure. The two tables (`SDK_RENDERERS`, `RUN_CODE_FLAVORS`) must stay in step, and that invariant is checked statically rather than left to review: both are `satisfies`-checked against that one union, so a language added to one and not the other fails `typecheck`. This is the mechanical form the drift risk deserves — the runtime `Object.hasOwn` guards would catch it too, but only once a backend reporting that language ships: one PR after the drift, at the consumer's integration point rather than where it was introduced, and on this base never, since no second backend exists. The tables keep their `Record` declared type because `CodeRuntime.language` is an unconstrained `string`; the union pins what the harness ships, the guards reject what a runtime reports. What stays outside that check is the prose that names the well-known values instead of deriving them: `dsh-code-runtime`'s README pair, its `CodeRuntime.language` JSDoc, and the `docs/core-data-structures/code-runtime.md` pair at the seam, plus this package's own README pair and its `Config.mode` JSDoc. Earlier notes name the values as the state at their own PR and are not on that list. Two separate reasons keep it ungated. Prose is not type-checked at all, wherever the union lives. And no type-level pin can stand in for it here: the interface package must not import its consumer's table, and `CodeRuntime.language` stays an unconstrained `string` by design, so moving the union into the seam would not apply it either. A unit test pinning the two key sets equal was rejected in favor of this: it would buy the same check at the cost of a test-only export of two private tables, and would run later than the compiler does. Which of the two runtime failures surfaces depends on the entry point, for a language absent from both tables: assembly reports the missing renderer, because `wireSchemas` calls `requireCodeRuntime` before projecting, while the public `schemas()` reaches `run_code`'s language-aware getters first and reports the missing flavor. The tool layer stays free of any concrete backend dependency, so it lands and is testable on master ahead of the Python protocol and backend. +Adding a backend language is three parallel edits — a `CodeSdkLanguage` member, an `SDK_RENDERERS` entry, and a `RUN_CODE_FLAVORS` entry — plus the renderer function the second points at, with no change to `agent-loop` or the registry structure. The two tables (`SDK_RENDERERS`, `RUN_CODE_FLAVORS`) must stay in step, and that invariant is checked statically rather than left to review: both are `satisfies`-checked against that one union, so a language added to one and not the other fails `typecheck`. This is the mechanical form the drift risk deserves — the runtime `Object.hasOwn` guards would catch it too, but only once a backend reporting that language ships: one PR after the drift, at the consumer's integration point rather than where it was introduced, and on this base never, since no second backend exists. The tables keep their `Record` declared type because `CodeRuntime.language` is an unconstrained `string`; the union pins what the harness ships, the guards reject what a runtime reports. What stays outside that check is the prose that names the well-known values instead of deriving them: `dsh-code-runtime`'s README pair, its `CodeRuntime.language` JSDoc, and the `docs/subsystems/code-runtime.md` pair at the seam, plus this package's own README pair and its `Config.mode` JSDoc. Earlier notes name the values as the state at their own PR and are not on that list. Two separate reasons keep it ungated. Prose is not type-checked at all, wherever the union lives. And no type-level pin can stand in for it here: the interface package must not import its consumer's table, and `CodeRuntime.language` stays an unconstrained `string` by design, so moving the union into the seam would not apply it either. A unit test pinning the two key sets equal was rejected in favor of this: it would buy the same check at the cost of a test-only export of two private tables, and would run later than the compiler does. Which of the two runtime failures surfaces depends on the entry point, for a language absent from both tables: assembly reports the missing renderer, because `wireSchemas` calls `requireCodeRuntime` before projecting, while the public `schemas()` reaches `run_code`'s language-aware getters first and reports the missing flavor. The tool layer stays free of any concrete backend dependency, so it lands and is testable on master ahead of the Python protocol and backend. The cost is that the Python branch of both tables is unreachable on this base: `CodeRuntime.language` is set by the loaded backend, the only published backend is `dsh-code-runtime-worker` (`'typescript'`), and the registry reads the loaded runtime rather than a config field, so no assembled application can select `renderToolsSdkPy` or `PYTHON_FLAVOR`. The model-visible surface is therefore unchanged by this note's work until a backend reporting `'python'` is published, and this PR's coverage is unit-level — the renderer output plus the dispatch and rejection paths. The keyless snapshot for the Python model interface belongs to the PR that publishes that backend, because only there does a real `cordis.yml` over published plugins produce a Python assembly; a snapshot example that mounted a fixture runtime here would assert against a test double, which [docs/testing.md](../../../../docs/testing.md) rejects as a substitute for the assembled application transcript. diff --git a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md index aa7eb2a6b4..1dbb3d889d 100644 --- a/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md +++ b/.agents/notes/implemented/feature/2026-07-31-code-mode-language-dispatch.zh.md @@ -17,7 +17,7 @@ Code Mode 只生成一种 SDK 形态:TypeScript。`ToolRegistry` 为 `tools:sd - `SDK_RENDERERS`(index.ts)把语言映射到它的 `tools:sdk` 渲染器——`typescript → renderToolsSdk`、`python → renderToolsSdkPy`。`tools:sdk` 段读取所加载运行时的语言并选出渲染器;`requireCodeRuntime` 拒绝其语言不在表中的 `mode: code`/`both` 运行时,并列出已知语言。 - `RUN_CODE_FLAVORS`(code-mode.ts)把语言映射到它那两条面向模型的 `run_code` 字符串(工具 `description` 与 `code` 参数描述),使一种语言的 SDK 段与它的传输 schema 始终一致。 -两张表在使用前都以 `Object.hasOwn` 读取,这样名为 `toString`/`constructor` 的语言不会把继承自 `Object.prototype` 的成员解析成渲染器。两个守卫的可达性不同:`SDK_RENDERERS` 的段内守卫不可达,因为 `requireCodeRuntime` 已在同一回调更早处校验过同一张 `const` 表(它带 `/* v8 ignore */`);而 `RUN_CODE_FLAVORS` 的守卫是主要的、可公开到达的拒绝路径——任何缺席 flavor 表的语言都经 `run_code` 的语言感知 getter 到达它,而公共 `schemas()` 抵达那些 getter 时并未先过 `requireCodeRuntime`;测试直读 definition 上的其中一个 getter,用的是对两张表都缺席的语言。「在 `SDK_RENDERERS` 里却不在 `RUN_CODE_FLAVORS` 里」这种漂移已由共享的 `CodeSdkLanguage` `satisfies` 在 `typecheck` 处拒绝,两个守卫都看不到这种输入;它们如今负责的是所挂载运行时报告了一门两张表都缺席的语言。schema 发射通过 `peekRuntime()` 而非 `requireRuntime()` 读取运行时:`undefined`(无运行时,由直读 definition 的读者与 `schemas()` 到达,其中 doc-catalog 采集是唯一已交付的一个,而它们都不会喂给模型,因为组装路径先过 `requireCodeRuntime`)降级到 TypeScript flavor,而挂载了未知语言则 fail loud——这不是下方被否决的静默回退,那指的是为真实运行时发出错误语言的 SDK。新增一门后端语言是三处并列编辑——一个 `CodeSdkLanguage` 成员加两条表项——再加它的渲染器,以及点名已知值而非从中派生的散文(seam 侧的 `dsh-code-runtime` README 双语对、它的 `CodeRuntime.language` JSDoc 与 `docs/core-data-structures/code-runtime.md` 双语对;本包自己的 README 双语对与它的 `Config.mode` JSDoc,无任何 gate 检查其中任何一处),不动 `agent-loop`,也不动注册表结构。 +两张表在使用前都以 `Object.hasOwn` 读取,这样名为 `toString`/`constructor` 的语言不会把继承自 `Object.prototype` 的成员解析成渲染器。两个守卫的可达性不同:`SDK_RENDERERS` 的段内守卫不可达,因为 `requireCodeRuntime` 已在同一回调更早处校验过同一张 `const` 表(它带 `/* v8 ignore */`);而 `RUN_CODE_FLAVORS` 的守卫是主要的、可公开到达的拒绝路径——任何缺席 flavor 表的语言都经 `run_code` 的语言感知 getter 到达它,而公共 `schemas()` 抵达那些 getter 时并未先过 `requireCodeRuntime`;测试直读 definition 上的其中一个 getter,用的是对两张表都缺席的语言。「在 `SDK_RENDERERS` 里却不在 `RUN_CODE_FLAVORS` 里」这种漂移已由共享的 `CodeSdkLanguage` `satisfies` 在 `typecheck` 处拒绝,两个守卫都看不到这种输入;它们如今负责的是所挂载运行时报告了一门两张表都缺席的语言。schema 发射通过 `peekRuntime()` 而非 `requireRuntime()` 读取运行时:`undefined`(无运行时,由直读 definition 的读者与 `schemas()` 到达,其中 doc-catalog 采集是唯一已交付的一个,而它们都不会喂给模型,因为组装路径先过 `requireCodeRuntime`)降级到 TypeScript flavor,而挂载了未知语言则 fail loud——这不是下方被否决的静默回退,那指的是为真实运行时发出错误语言的 SDK。新增一门后端语言是三处并列编辑——一个 `CodeSdkLanguage` 成员加两条表项——再加它的渲染器,以及点名已知值而非从中派生的散文(seam 侧的 `dsh-code-runtime` README 双语对、它的 `CodeRuntime.language` JSDoc 与 `docs/subsystems/code-runtime.md` 双语对;本包自己的 README 双语对与它的 `Config.mode` JSDoc,无任何 gate 检查其中任何一处),不动 `agent-loop`,也不动注册表结构。 `code-mode.ts` 只依赖运行时 seam(`@deepseek-ai/dsh-code-runtime`),绝不依赖具体后端;分发在运行时按 `runtime.language` 进行。因此工具层独立于协议和后端 PR 落地——它只需要 seam 的 `language` 字段,而该字段已在 master 上。 @@ -37,7 +37,7 @@ Code Mode 只生成一种 SDK 形态:TypeScript。`ToolRegistry` 为 `tools:sd ## Consequences -新增一门后端语言是三处并列编辑——一个 `CodeSdkLanguage` 成员、一个 `SDK_RENDERERS` 表项、一个 `RUN_CODE_FLAVORS` 表项——再加第二处所指向的渲染器函数,不动 `agent-loop`,也不动注册表结构。两张表(`SDK_RENDERERS`、`RUN_CODE_FLAVORS`)必须同步,且这条不变式由静态检查把关,而非交给 review:两张表都以 `satisfies` 对上述同一个 union 校验,因此只加其一而漏掉另一会在 `typecheck` 处失败。这正是该漂移风险应有的机械形式——运行期的 `Object.hasOwn` 守卫同样能捕获,但要等到有后端报告该语言之后:晚于漂移引入一个 PR,且触发点在消费方的集成处而非漂移引入处;在当前 base 上则永远不会触发,因为不存在第二个后端。两张表的声明类型仍是 `Record`,因为 `CodeRuntime.language` 是不受约束的 `string`:union 钉住本仓库交付了什么,守卫拒绝运行时报告了什么。落在这条检查之外的是点名已知值而非从中派生的散文:seam 侧的 `dsh-code-runtime` README 双语对、它的 `CodeRuntime.language` JSDoc 与 `docs/core-data-structures/code-runtime.md` 双语对,再加本包自己的 README 双语对与它的 `Config.mode` JSDoc。更早的 note 点名这些值时记的是其自身 PR 当时的状态,不在此列。让它无 gate 的是两条独立理由。其一,散文根本不受类型检查,union 放在哪里都一样。其二,类型级替代在这里也不可用:接口包不得 import 其消费方的表,而 `CodeRuntime.language` 按设计保持不受约束的 `string`,即便把 union 迁进 seam 也不会作用到它。用一个断言两张表键集相等的 unit test 的方案被否决:它买到的是同一条检查,代价却是把两张私有表做测试专用导出,且运行时机晚于编译器。对两张表都缺席的语言,两种运行期失败中报出哪一条随入口而异:组装路径报缺渲染器,因为 `wireSchemas` 在投影前先调 `requireCodeRuntime`;而公共 `schemas()` 先经过 `run_code` 的语言感知 getter,报的是缺 flavor 表项。工具层不依赖任何具体后端,因此它能先于 Python 协议和后端在 master 上落地并可测。 +新增一门后端语言是三处并列编辑——一个 `CodeSdkLanguage` 成员、一个 `SDK_RENDERERS` 表项、一个 `RUN_CODE_FLAVORS` 表项——再加第二处所指向的渲染器函数,不动 `agent-loop`,也不动注册表结构。两张表(`SDK_RENDERERS`、`RUN_CODE_FLAVORS`)必须同步,且这条不变式由静态检查把关,而非交给 review:两张表都以 `satisfies` 对上述同一个 union 校验,因此只加其一而漏掉另一会在 `typecheck` 处失败。这正是该漂移风险应有的机械形式——运行期的 `Object.hasOwn` 守卫同样能捕获,但要等到有后端报告该语言之后:晚于漂移引入一个 PR,且触发点在消费方的集成处而非漂移引入处;在当前 base 上则永远不会触发,因为不存在第二个后端。两张表的声明类型仍是 `Record`,因为 `CodeRuntime.language` 是不受约束的 `string`:union 钉住本仓库交付了什么,守卫拒绝运行时报告了什么。落在这条检查之外的是点名已知值而非从中派生的散文:seam 侧的 `dsh-code-runtime` README 双语对、它的 `CodeRuntime.language` JSDoc 与 `docs/subsystems/code-runtime.md` 双语对,再加本包自己的 README 双语对与它的 `Config.mode` JSDoc。更早的 note 点名这些值时记的是其自身 PR 当时的状态,不在此列。让它无 gate 的是两条独立理由。其一,散文根本不受类型检查,union 放在哪里都一样。其二,类型级替代在这里也不可用:接口包不得 import 其消费方的表,而 `CodeRuntime.language` 按设计保持不受约束的 `string`,即便把 union 迁进 seam 也不会作用到它。用一个断言两张表键集相等的 unit test 的方案被否决:它买到的是同一条检查,代价却是把两张私有表做测试专用导出,且运行时机晚于编译器。对两张表都缺席的语言,两种运行期失败中报出哪一条随入口而异:组装路径报缺渲染器,因为 `wireSchemas` 在投影前先调 `requireCodeRuntime`;而公共 `schemas()` 先经过 `run_code` 的语言感知 getter,报的是缺 flavor 表项。工具层不依赖任何具体后端,因此它能先于 Python 协议和后端在 master 上落地并可测。 代价是两张表的 Python 分支在当前 base 上不可达:`CodeRuntime.language` 由所加载的后端设定,已发布的后端只有 `dsh-code-runtime-worker`(`'typescript'`),而注册表读取的是所加载的运行时而非某个配置字段,因此没有任何一份组装好的应用能选中 `renderToolsSdkPy` 或 `PYTHON_FLAVOR`。也就是说,在报告 `'python'` 的后端发布之前,本 note 的工作不改变模型可见表面,本 PR 的覆盖因此是 unit 级——渲染器输出加分发与拒绝路径。Python 模型界面的 keyless snapshot 归属于发布该后端的那个 PR,因为只有在那里,一份基于已发布插件的真实 `cordis.yml` 才会产出 Python 组装;在此处挂载 fixture 运行时的快照示例断言的是测试替身,而 [docs/testing.md](../../../../docs/testing.md) 明确拒绝以此替代组装好的应用 transcript。 diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml index 4acabc8700..13c54fa458 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md -2026-06-20-core-data-structures-catalog.md: 7ee1e0ac3df7cb37fc9797702d44f409da820a94 -2026-06-20-core-data-structures-catalog.zh.md: 7cb0ae216f5c5f429c18d097862350997a8335d3 +2026-06-20-core-data-structures-catalog.md: 57433fdcb3c77976c4ba0cfe7d157f94dff331dd +2026-06-20-core-data-structures-catalog.zh.md: 3956839c8211e294280805d9b6993e5607bfe04f diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index 7ee1e0ac3d..57433fdcb3 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -1,4 +1,4 @@ -# Agent Note: Core-data-structures catalog and the `ts type-equiv` drift gate +# Agent Note: Subsystems catalog and the `ts type-equiv` drift gate Status: implemented @@ -12,7 +12,7 @@ So the work had two intertwined questions: **what belongs in such a catalog** (t ## Decision -A new `docs/core-data-structures/` folder catalogs the vocabulary, with a new `verify-type-equiv` doc-sync gate that keeps every pasted type declaration and its JSDoc synchronized with source. +A new `docs/subsystems/` folder catalogs the vocabulary, with a new `verify-type-equiv` doc-sync gate that keeps every pasted type declaration and its JSDoc synchronized with source. ### What counts as "core" — the spine-vs-seam line diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index 7cb0ae216f..3956839c82 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -12,7 +12,7 @@ Status: implemented ## 决策 -新增的 `docs/core-data-structures/` 目录对这些词汇编目,并配有新的 `verify-type-equiv` doc-sync(文档同步门禁),使每个粘贴的类型声明及其 JSDoc 与源码保持同步。 +新增的 `docs/subsystems/` 目录对这些词汇编目,并配有新的 `verify-type-equiv` doc-sync(文档同步门禁),使每个粘贴的类型声明及其 JSDoc 与源码保持同步。 ### 何为「核心」——主干与 seam 的分界线 diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml index 70d56bbca5..5ef82c77de 100644 --- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml +++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md -2026-06-22-fork-child-replay-seed-boundary.md: ed3ec095bc14128f5ebc0a9188bc022ef97b1c8b -2026-06-22-fork-child-replay-seed-boundary.zh.md: 1d938cbf6c9c32a144d58fed48e552d85aa9c625 +2026-06-22-fork-child-replay-seed-boundary.md: 27db3b768f2503121b0bc8912f9bc0fecb00a10d +2026-06-22-fork-child-replay-seed-boundary.zh.md: f64fc55fc63e2caa4b50112407b645e6e7f12b0f diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md index ed3ec095bc..27db3b768f 100644 --- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md +++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md @@ -44,6 +44,6 @@ This closes the routing correctness gap, and two recorded fork scenarios exercis ## Consequences -- A new persisted header field across core + both backends; the core-data-structures catalog (`persistence.md`) is updated in the same change (its `SessionHeader` / `CreateSessionOptions` `type-equiv` blocks). +- A new persisted header field across core + both backends; the subsystems catalog (`persistence.md`) is updated in the same change (its `SessionHeader` / `CreateSessionOptions` `type-equiv` blocks). - Existing SQLite databases at schema v2 are rejected on open (no user data pre-release). - Spawn replay is unchanged (`seedLength` 0). Fork replay now routes a child to its own script; covered by a regression in `llm-replay`'s tests (a child fixture whose seeded prefix carries a parent chunk — the derived child script must exclude it, proven red without the slice) and a persistence round-trip test (both backends, via the shared coordinator contract). diff --git a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md index 1d938cbf6c..f64fc55fc6 100644 --- a/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md +++ b/.agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.zh.md @@ -44,6 +44,6 @@ subagent 脚本由 [`deriveReplayScript`](../../../../packages/support/llm-repla ## 后果 -- core 与两个后端新增一个持久化 header 字段;核心数据结构目录(`persistence.md`)在同一变更中更新(其 `SessionHeader` / `CreateSessionOptions` 的 `type-equiv` 块)。 +- core 与两个后端新增一个持久化 header 字段;子系统目录(`persistence.md`)在同一变更中更新(其 `SessionHeader` / `CreateSessionOptions` 的 `type-equiv` 块)。 - 既有的 schema v2 SQLite 数据库在打开时被拒绝(预发布阶段无用户数据)。 - spawn 回放不变(`seedLength` 为 0)。fork 回放现在将子会话路由到自身的脚本;由 `llm-replay` 测试中的一个回归用例覆盖(一个子会话 fixture,其播种前缀包含父会话的分片——推导出的子会话脚本必须排除它,不做 slice 时该用例为红)以及一个持久化往返测试(两个后端,通过共享的 coordinator 契约)。 diff --git a/.agents/skills/dsh-code-review/SKILL.md b/.agents/skills/dsh-code-review/SKILL.md index f933beb86b..81f63fac2f 100644 --- a/.agents/skills/dsh-code-review/SKILL.md +++ b/.agents/skills/dsh-code-review/SKILL.md @@ -21,7 +21,7 @@ description: Use when reviewing a pull request in the deepseek-harness repo — 1. **New prose receives semantic review.** Use [dsh-prose-standard](../dsh-prose-standard/SKILL.md) to critically review every added or changed Markdown passage, JSDoc, comment, prompt, description, diagnostic, and visible string. Verify required coverage, accuracy, placement, and editorial quality against the owning code or behavior; automated checks do not establish those properties. 2. **Docs match the code.** Config, defaults, errors, wire fields, events, and public behavior update the package README and JSDoc in the same diff. Comments state non-obvious contracts; flag implementation narration, test walkthroughs, review history, and duplicated rationale for deletion or a link to their one home. -3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [core-data-structures](../../../docs/core-data-structures/core.md) page and any `type-equiv` entry. Internal types need no catalog entry. +3. **Core type docs match.** Changes to spine or seam vocabulary update the appropriate [subsystems](../../../docs/subsystems/core.md) page and any `type-equiv` entry. Internal types need no catalog entry. 4. **Registrations clean up.** Verify each new registry contribution satisfies the disposal-test contract in [packages/AGENTS.md](../../../packages/AGENTS.md). 5. **Invariant companions are semantic.** For every touched `./invariant`, require an owner event-stream or mutable-data relationship at its authoritative boundary; service or method presence, plugin metadata or effects, and fixed pure examples belong in type, load, or unit tests. Accept an empty installer when its package-specific reason establishes that no plausible runtime relationship exists; do not demand an invented check merely to eliminate emptiness ([repository rule](../../../AGENTS.md#conventions); [package contract](../../../packages/AGENTS.md)). 6. **Required evidence exists.** Verify the author ran the [relevant local checks](../../../AGENTS.md#run-relevant-checks-locally) for the diff and that CI covers the exhaustive matrix; review the semantic gaps neither can detect. diff --git a/docs/AGENTS.md b/docs/AGENTS.md index cd4e8aaaa5..3bbd4f330d 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -21,7 +21,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 | | 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 | -| [core-data-structures/](core-data-structures/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 | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the Agent Note each guide links) | @@ -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)). - **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)). -- **The [core-data-structures catalog](core-data-structures/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](core-data-structures/core.md#what-counts-as-core)). +- **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)). - **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. - Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams". diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 76491a37fe..15a6ba3fb5 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: cee159452fc536c98a006cfa3ea92e9d21a1e77b -architecture.zh.md: cf10e60a5d5e4fe3f39d72bef3d48fe7c2f3c105 +architecture.md: 7baf550128fe83528ce12bf2f9b40cfc8f78e322 +architecture.zh.md: 076edd0549fb7f648a7a1b06d7027a1e6eca735f diff --git a/docs/architecture.md b/docs/architecture.md index cee159452f..7baf550128 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -123,7 +123,7 @@ Adapter selection, dispatch, and iteration failures become terminal error or abo Other failures use `agent/error`; cancellation and disposal beat recovery. Before request-header commit, the turn signal cancels capability preparation; undispatched tools get synthetic `tool/call`/`ABORTED_BEFORE_DISPATCH` pairs. Effective `cancel(cause)` reports its cause before clearing and aborting; idle calls emit nothing. Waking input that lands after the abort fires but before convergence runs at the driver's convergence boundary, while a `disposed` cancel leaves it parked ([cancel-convergence wake latch](../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)). Durability distinguishes `aborted` cancellation from `disposed` teardown, which awaits quiescence ([decision](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)). -Turn and step events are turn-enclosed; the loop appends `user/message` events only from entered batches inside a turn. A turn opens before the initial claim and pre-step, so rejection, empty input, cancellation, or failure closes a durable turn without any step events. Standalone `compact/* { turn: null }` events consume no turn, and their lock-time markers may interleave with inbox splices. Reload synthesizes interrupted turn ends; `session/end-seed` distinguishes stale compaction orphans from live locks. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap). +Turn and step events are turn-enclosed; the loop appends `user/message` events only from entered batches inside a turn. A turn opens before the initial claim and pre-step, so rejection, empty input, cancellation, or failure closes a durable turn without any step events. Standalone `compact/* { turn: null }` events consume no turn, and their lock-time markers may interleave with inbox splices. Reload synthesizes interrupted turn ends; `session/end-seed` distinguishes stale compaction orphans from live locks. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](subsystems/session.md#why-a-turn-ended-turnendreasonmap). ### Agent Handles @@ -147,9 +147,9 @@ Between turns, owners append log-only events through `Session`, flushing only fo ### Model Content -Messages use typed blocks from merge-extensible `ContentBlockMap`; the pattern also types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in [token-meter.md](core-data-structures/token-meter.md). +Messages use typed blocks from merge-extensible `ContentBlockMap`; the pattern also types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in [token-meter.md](subsystems/token-meter.md). -Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report normalized failure facts, and a handling `agent/request-error` plugin returns a retry action. The loop logs chunks, successful provenance, and replay state. Remote adapters use per-read idle watchdogs. Replay crosses routes only through a shared adapter instance ([contract](core-data-structures/llm-streaming.md)). +Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report normalized failure facts, and a handling `agent/request-error` plugin returns a retry action. The loop logs chunks, successful provenance, and replay state. Remote adapters use per-read idle watchdogs. Replay crosses routes only through a shared adapter instance ([contract](subsystems/llm-streaming.md)). ## Extension And Composition @@ -157,7 +157,7 @@ Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is on Capabilities separate **interface / implementation / consumer** layers. Filesystem and subprocess providers define one execution world; Bash, PTY, and LSP run there without provider forks. See the [capability graph](capability-seams.md). -Exceptions combine LLM interface/consumer, filesystem policy, web registries, and named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, use ACP children, or delegate one self-contained turn to a real product provider such as Codex ([subagent.md](core-data-structures/subagent.md)). +Exceptions combine LLM interface/consumer, filesystem policy, web registries, and named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, use ACP children, or delegate one self-contained turn to a real product provider such as Codex ([subagent.md](subsystems/subagent.md)). `dsh-workspace-context` composes its baseline on the first `agent/pre-step` and folds it into the final entering batch right after the claimed prompt, so it reaches the first request with the direct prompt; rejection keeps it in the next-step inbox. When compaction removes that baseline from the visible surface, the next entering pre-step composes the current baseline and carries it in the same request. Filesystem changes projected after tools are likewise folded into the next entering pre-step instead of creating a later context-only step ([decision](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)). `dsh-paths` owns shared paths. diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index cf10e60a5d..076edd0549 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -123,7 +123,7 @@ idle inject: 其他故障使用 `agent/error`;取消和资源释放优先于恢复。在提交请求头之前,轮次信号会取消功能准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 会在清空队列和中止前报告原因;空闲调用不发事件。abort 触发后、收敛前到达的唤醒输入会在 driver 的收敛边界执行,而 `disposed` 取消则将其停放([取消收敛窗口唤醒锁存](../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md))。持久化层以 `aborted` 区分取消,以 `disposed` 区分会等待完全停稳的拆卸([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。 -轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加 `user/message`。轮次会在首次领取与 pre-step 之前打开,因此拒绝、空输入、取消或失败会关闭一个不包含任何步骤事件的持久轮次。独立的 `compact/* { turn: null }` 事件不占用轮次,其锁定时刻标记可以与 inbox splice 交错。重新加载会为中断的轮次合成结束事件;`session/end-seed` 区分陈旧的压缩遗留项与活跃锁。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。 +轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加 `user/message`。轮次会在首次领取与 pre-step 之前打开,因此拒绝、空输入、取消或失败会关闭一个不包含任何步骤事件的持久轮次。独立的 `compact/* { turn: null }` 事件不占用轮次,其锁定时刻标记可以与 inbox splice 交错。重新加载会为中断的轮次合成结束事件;`session/end-seed` 区分陈旧的压缩遗留项与活跃锁。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](subsystems/session.md#why-a-turn-ended-turnendreasonmap)。 ### Agent 句柄 @@ -147,9 +147,9 @@ idle inject: ### 模型内容 -消息使用从可合并扩展的 `ContentBlockMap` 派生的类型化块;同一模式也为 `MessageSource`、`FinishReason`、`TurnTrigger` 和 `TurnEndReason` 定义类型。新增块会协调适配器、UI、压缩、token 计量和持久化;回放计量见 [token-meter.md](core-data-structures/token-meter.md)。 +消息使用从可合并扩展的 `ContentBlockMap` 派生的类型化块;同一模式也为 `MessageSource`、`FinishReason`、`TurnTrigger` 和 `TurnEndReason` 定义类型。新增块会协调适配器、UI、压缩、token 计量和持久化;回放计量见 [token-meter.md](subsystems/token-meter.md)。 -流式输出使用原始分片和 `BlockAssembler`。每次 `LlmAdapter.stream()` 调用代表一次提供方尝试;适配器报告标准化的故障事实,负责处理的 `agent/request-error` 插件会返回重试动作。循环会记录分片、成功结果的来源信息和回放状态。远程适配器使用逐次读取空闲看门狗。回放仅通过共用的适配器实例跨路由传递([契约](core-data-structures/llm-streaming.md))。 +流式输出使用原始分片和 `BlockAssembler`。每次 `LlmAdapter.stream()` 调用代表一次提供方尝试;适配器报告标准化的故障事实,负责处理的 `agent/request-error` 插件会返回重试动作。循环会记录分片、成功结果的来源信息和回放状态。远程适配器使用逐次读取空闲看门狗。回放仅通过共用的适配器实例跨路由传递([契约](subsystems/llm-streaming.md))。 ## 扩展与组合 @@ -157,7 +157,7 @@ idle inject: 能力分为**接口/实现/消费方**三层。文件系统与进程管理提供方共同定义一个执行世界;Bash、PTY 和 LSP 都在其中运行,无需提供方专用 fork。参见[功能图](capability-seams.md)。 -例外情况包括 LLM(大语言模型)合并接口和消费方、文件系统整合策略、web 使用注册表、skill 和 subagent 使用具名提供方。subagent 可以通过 spawn 创建全新实例、fork 一个已完成轮次的前缀、使用 ACP(Agent Client Protocol)子 agent,或将一个独立完整的轮次委派给 Codex 等真实产品提供方([subagent.md](core-data-structures/subagent.md))。 +例外情况包括 LLM(大语言模型)合并接口和消费方、文件系统整合策略、web 使用注册表、skill 和 subagent 使用具名提供方。subagent 可以通过 spawn 创建全新实例、fork 一个已完成轮次的前缀、使用 ACP(Agent Client Protocol)子 agent,或将一个独立完整的轮次委派给 Codex 等真实产品提供方([subagent.md](subsystems/subagent.md))。 `dsh-workspace-context` 在第一次 `agent/pre-step` 组合基线并将它折入最终进入的批次、紧随已领取的直接提示词之后,使其与直接提示词一同抵达第一次请求;reject 则将它留在 next-step inbox。当压缩从可见表层移除该基线时,下一次进入步骤的 pre-step 会组合当前基线,并在同一请求中携带它。工具执行后投影的文件系统变更也会折入下一次进入步骤的 pre-step,而不会另外创建稍后的纯上下文步骤([决策](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md))。`dsh-paths` 负责共享路径。 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 254d85ccd1..850f4731e2 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -3,7 +3,7 @@ # Plugin Config Catalog -Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin's full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [core-data-structures/](core-data-structures/core.md) documents the types these declarations reference. +Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin's full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference. This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verified fresh by `pnpm run verify-config-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks use a `ts config-catalog` fence (skipped by doc-typecheck, since a lone declaration referencing imports is not standalone-compilable). The generator also cross-checks the runtime schemastery schema against the pasted declaration — every schema-validated key, nested keys included, must be locatable on the declared config type — so the paste cannot hide a loader-accepted field. @@ -106,7 +106,7 @@ export interface Config { } ``` -Depends on: [`AgentOptions`](core-data-structures/core.md) · [`SessionId`](core-data-structures/core.md) +Depends on: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/core.md) Source: [`packages/core/agent-loop/src/index.ts:236`](../packages/core/agent-loop/src/index.ts) @@ -1044,7 +1044,7 @@ export interface PresetSpec { } ``` -Depends on: [`ApprovalPolicy`](core-data-structures/approval.md) · [`SandboxMode`](core-data-structures/sandbox.md) +Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md) Source: [`packages/interaction/permission/src/index.ts:140`](../packages/interaction/permission/src/index.ts) @@ -1234,7 +1234,7 @@ export interface Config { } ``` -Depends on: [`SandboxMode`](core-data-structures/sandbox.md) +Depends on: [`SandboxMode`](subsystems/sandbox.md) Source: [`packages/sandbox/sandbox-policy/src/index.ts:67`](../packages/sandbox/sandbox-policy/src/index.ts) @@ -2193,7 +2193,7 @@ export interface Config { } ``` -Depends on: [`AgentOptions`](core-data-structures/core.md) +Depends on: [`AgentOptions`](subsystems/core.md) Source: [`packages/subagent/tool-subagent/src/index.ts:25`](../packages/subagent/tool-subagent/src/index.ts) @@ -2212,7 +2212,7 @@ export interface Config { } ``` -Depends on: [`SubagentReportDelivery`](core-data-structures/subagent.md) +Depends on: [`SubagentReportDelivery`](subsystems/subagent.md) Source: [`packages/subagent/tool-subagent-report/src/index.ts:22`](../packages/subagent/tool-subagent-report/src/index.ts) diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index 2c99c451d2..dd9cf5bfa5 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -3,7 +3,7 @@ # 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.` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around. +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.` surface is the sibling [services catalog](services.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around. 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. @@ -31,7 +31,7 @@ A fully configured agent and live session were published. Setup is composition-o 'agent/created'(this: Scoped, payload: { agent: Agent }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:158`](../../packages/core/agent/src/types.ts) @@ -51,7 +51,7 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco 'agent/disposed'(this: Scoped, payload: { agent: Agent }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/types.ts) @@ -73,7 +73,7 @@ A step or turn errored. The machine reports a failure here even when the error h 'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) @@ -95,7 +95,7 @@ One message left the inbox inside its open turn. If the proposed step is rejecte 'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) @@ -114,7 +114,7 @@ One message was discarded from the live inbox. 'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) @@ -133,7 +133,7 @@ One message entered the live inbox. 'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) @@ -156,7 +156,7 @@ Reject a proposed step or replace the messages that enter it. Calling `next()` p 'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [PreStepDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md) +Types: [Agent](../subsystems/core.md) · [PreStepDecision](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) Source: [`packages/core/agent/src/types.ts:230`](../../packages/core/agent/src/types.ts) @@ -180,7 +180,7 @@ Replace the frozen call configuration. `await next()` yields the config the mach 'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [LlmCallConfig](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [LlmCallConfig](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) @@ -207,7 +207,7 @@ Handle one failed model-request attempt before the loop retries or closes its st 'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [LlmFailure](../core-data-structures/llm-streaming.md) · [RequestErrorAction](../core-data-structures/core.md) · [ResolvedRetryPolicy](../core-data-structures/llm-streaming.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [LlmFailure](../subsystems/llm-streaming.md) · [RequestErrorAction](../subsystems/core.md) · [ResolvedRetryPolicy](../subsystems/llm-streaming.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) @@ -229,7 +229,7 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to 'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [SessionStartSource](../core-data-structures/core.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [SessionStartSource](../subsystems/core.md) Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/types.ts) @@ -250,7 +250,7 @@ Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` 'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void ``` -Types: [Agent](../core-data-structures/core.md) · [AgentStatus](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [AgentStatus](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/types.ts) @@ -279,7 +279,7 @@ The turn is about to close: the model owes no response (no live tool calls, no f 'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void ``` -Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) Source: [`packages/core/agent/src/types.ts:277`](../../packages/core/agent/src/types.ts) @@ -302,7 +302,7 @@ A declarative agent entry failed before it could publish a live agent. Consumers 'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void ``` -Types: [SessionId](../core-data-structures/core.md) +Types: [SessionId](../subsystems/core.md) Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) @@ -323,7 +323,7 @@ Ask composed answerers for one decision. Return an outcome to claim the request 'approval/request'(this: Scoped, req: ApprovalRequest, next: () => Promise): Promise ``` -Types: [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [ApprovalService](../core-data-structures/approval.md) · [Scoped](../core-data-structures/scope.md) +Types: [ApprovalOutcome](../subsystems/approval.md) · [ApprovalRequest](../subsystems/approval.md) · [ApprovalService](../subsystems/approval.md) · [Scoped](../subsystems/scope.md) Source: [`packages/interaction/user-approval/src/index.ts:30`](../../packages/interaction/user-approval/src/index.ts) @@ -367,7 +367,7 @@ Committed change to a provider-managed credential source: a `set`, an `unset`, o 'credentials/updated'(ref: CredentialRef): void ``` -Types: [CredentialRef](../core-data-structures/credentials.md) +Types: [CredentialRef](../subsystems/credentials.md) Source: [`packages/credentials/credentials/src/index.ts:67`](../../packages/credentials/credentials/src/index.ts) @@ -408,7 +408,7 @@ Single-slot decision for the next FileSystem.editText. Calling `next()` yields a 'fs/edit-intent'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined> ``` -Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) +Types: [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) Source: [`packages/fs/fs/src/index.ts:64`](../../packages/fs/fs/src/index.ts) @@ -428,7 +428,7 @@ Record a successful observation. Listeners must be synchronous recorders: throws 'fs/observed'(target: FsTarget, version: FsVersion, actor: object | undefined): void ``` -Types: [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) +Types: [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) Source: [`packages/fs/fs/src/index.ts:73`](../../packages/fs/fs/src/index.ts) @@ -448,7 +448,7 @@ Single-slot decision for the next FileSystem.writeText. Calling `next()` yields 'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise ``` -Types: [FsTarget](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) +Types: [FsTarget](../subsystems/filesystem.md) · [FsWriteIntent](../subsystems/filesystem.md) Source: [`packages/fs/fs/src/index.ts:56`](../../packages/fs/fs/src/index.ts) @@ -470,7 +470,7 @@ Goal mutation accepted by one live agent. The matching `goal/change` session eve 'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped, payload: { agent: Agent; change: GoalChanged }): void ``` -Types: [Agent](../core-data-structures/core.md) · [GoalChanged](../core-data-structures/goal.md) · [Scoped](../core-data-structures/scope.md) +Types: [Agent](../subsystems/core.md) · [GoalChanged](../subsystems/goal.md) · [Scoped](../subsystems/scope.md) Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts) @@ -515,7 +515,7 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Types: [GenerateOptions](../core-data-structures/core.md) · [LlmService](../core-data-structures/llm-streaming.md) · [StreamChunk](../core-data-structures/llm-streaming.md) +Types: [GenerateOptions](../subsystems/core.md) · [LlmService](../subsystems/llm-streaming.md) · [StreamChunk](../subsystems/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) @@ -540,7 +540,7 @@ Creation announcement during session publication. A synchronous throw vetoes and 'session/created'(this: Scoped, session: Session): void ``` -Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) +Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) Source: [`packages/core/session/src/index.ts:74`](../../packages/core/session/src/index.ts) @@ -561,7 +561,7 @@ Emitted once when an announced session leaves the store, including publication r 'session/disposed'(this: Scoped, session: Session): void ``` -Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) +Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) Source: [`packages/core/session/src/index.ts:84`](../../packages/core/session/src/index.ts) @@ -584,7 +584,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before 'session/event'(this: Scoped, session: Session, event: SessionEvent): void ``` -Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) +Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md) Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) @@ -604,7 +604,7 @@ Awaited parallel durability checkpoint: every listener runs and the caller await 'session/flush'(this: Scoped, session: Session): Promise | void ``` -Types: [Scoped](../core-data-structures/scope.md) · [Session](../core-data-structures/session.md) +Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) Source: [`packages/core/session/src/index.ts:105`](../../packages/core/session/src/index.ts) @@ -629,7 +629,7 @@ One registered namespace's RAW user section changed, whether or not the resolved 'settings/document-updated'(ns: SettingsNamespace, revision: number): void ``` -Types: [SettingsNamespace](../core-data-structures/settings.md) +Types: [SettingsNamespace](../subsystems/settings.md) Source: [`packages/settings/settings/src/index.ts:170`](../../packages/settings/settings/src/index.ts) @@ -656,7 +656,7 @@ Committed change to one registered namespace's resolved value. Emitted after the 'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void ``` -Types: [SettingsNamespace](../core-data-structures/settings.md) · [SettingsUpdateSource](../core-data-structures/settings.md) +Types: [SettingsNamespace](../subsystems/settings.md) · [SettingsUpdateSource](../subsystems/settings.md) Source: [`packages/settings/settings/src/index.ts:157`](../../packages/settings/settings/src/index.ts) @@ -697,7 +697,7 @@ A published child settled. Scope-filtered dispatch uses the same delegating pare 'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void ``` -Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md) +Types: [Scoped](../subsystems/scope.md) · [SubagentService](../subsystems/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts) @@ -714,7 +714,7 @@ A provider became resolvable in the registry. 'subagent/provider-added'(provider: SubagentProvider): void ``` -Types: [SubagentProvider](../core-data-structures/subagent.md) +Types: [SubagentProvider](../subsystems/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts) @@ -751,7 +751,7 @@ A provider established a published child. For in-process providers, `ctx.agents. 'subagent/start'(this: Scoped, info: SubagentRunInfo): void ``` -Types: [Scoped](../core-data-structures/scope.md) · [SubagentService](../core-data-structures/subagent.md) +Types: [Scoped](../subsystems/scope.md) · [SubagentService](../subsystems/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts) @@ -775,7 +775,7 @@ Expert waterfall over the assembled sections, contexts, tools, and variables. Sc 'system-prompt/assemble'(this: Scoped, assembly: PromptAssembly, context: AssembleContext, next: () => Promise): Promise ``` -Types: [AssembleContext](../core-data-structures/system-prompt.md) · [Scoped](../core-data-structures/scope.md) · [SystemPrompt](../core-data-structures/system-prompt.md) +Types: [AssembleContext](../subsystems/system-prompt.md) · [Scoped](../subsystems/scope.md) · [SystemPrompt](../subsystems/system-prompt.md) Source: [`packages/core/system-prompt/src/index.ts:29`](../../packages/core/system-prompt/src/index.ts) @@ -865,7 +865,7 @@ Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bri 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise ``` -Types: [CodeDispatchLog](../core-data-structures/tools.md) · [ContentBlock](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [ToolRegistry](../core-data-structures/tools.md) +Types: [CodeDispatchLog](../subsystems/tools.md) · [ContentBlock](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [ToolRegistry](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) @@ -887,7 +887,7 @@ Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a nor 'tools/execute'(this: Scoped, exec: ToolDispatchExecution, next: () => Promise): Promise ``` -Types: [Scoped](../core-data-structures/scope.md) · [ToolDispatchExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md) +Types: [Scoped](../subsystems/scope.md) · [ToolDispatchExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:148`](../../packages/core/tools/src/index.ts) @@ -910,7 +910,7 @@ Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts 'tools/post-execute'(this: Scoped, exec: ToolExecution, result: Readonly, next: () => Promise): Promise ``` -Types: [PostToolDecision](../core-data-structures/tools.md) · [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md) +Types: [PostToolDecision](../subsystems/tools.md) · [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:160`](../../packages/core/tools/src/index.ts) @@ -931,7 +931,7 @@ Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approv 'tools/pre-execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise ``` -Types: [PreToolDecision](../core-data-structures/tools.md) · [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md) +Types: [PreToolDecision](../subsystems/tools.md) · [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:137`](../../packages/core/tools/src/index.ts) @@ -950,7 +950,7 @@ Observe the frozen, lossless-JSON final outcome. Listener failures are contained 'tools/result'(this: Scoped, exec: Readonly, result: Readonly): undefined ``` -Types: [Scoped](../core-data-structures/scope.md) · [ToolExecution](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolRegistry](../core-data-structures/tools.md) +Types: [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:181`](../../packages/core/tools/src/index.ts) @@ -974,7 +974,7 @@ One `agent()` call settled (clean result, child failure, or run cancellation). P 'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:81`](../../packages/workflow/workflow/src/index.ts) @@ -995,7 +995,7 @@ One `agent()` call established a published child run. Paired with Events['workfl 'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:70`](../../packages/workflow/workflow/src/index.ts) @@ -1016,7 +1016,7 @@ A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves 'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:91`](../../packages/workflow/workflow/src/index.ts) @@ -1034,7 +1034,7 @@ The script emitted a narration line (a `log(message)` call). 'workflow/log'(info: WorkflowRunInfo, message: string): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:60`](../../packages/workflow/workflow/src/index.ts) @@ -1053,7 +1053,7 @@ The script entered a phase (a `phase(title)` call) — progress grouping for obs 'workflow/phase'(info: WorkflowRunInfo, title: string): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:53`](../../packages/workflow/workflow/src/index.ts) @@ -1071,7 +1071,7 @@ A workflow run started — the script's meta block validated, the body about to 'workflow/start'(info: WorkflowRunInfo): void ``` -Types: [WorkflowRunInfo](../core-data-structures/workflow.md) +Types: [WorkflowRunInfo](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:45`](../../packages/workflow/workflow/src/index.ts) diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 56a0d0d2e6..dbab056105 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -3,7 +3,7 @@ # Cordis Services Catalog -Every `ctx.` 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 [core-data-structures/](../core-data-structures/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. +Every `ctx.` 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. 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. @@ -42,7 +42,7 @@ async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [AgentOptions](../core-data-structures/core.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) +Types: [Agent](../subsystems/core.md) · [AgentOptions](../subsystems/core.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) Source: [`packages/core/agent-loop/src/index.ts:277`](../../packages/core/agent-loop/src/index.ts) @@ -214,7 +214,7 @@ list(): Agent[] roots(): Agent[] ``` -Types: [Agent](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) +Types: [Agent](../subsystems/core.md) · [SessionId](../subsystems/core.md) Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) @@ -260,7 +260,7 @@ async request(req: ApprovalRequest): Promise overrideOf(session: Session): ApprovalPolicy | undefined ``` -Types: [Agent](../core-data-structures/core.md) · [ApprovalOutcome](../core-data-structures/approval.md) · [ApprovalPolicy](../core-data-structures/approval.md) · [ApprovalRequest](../core-data-structures/approval.md) · [Session](../core-data-structures/session.md) +Types: [Agent](../subsystems/core.md) · [ApprovalOutcome](../subsystems/approval.md) · [ApprovalPolicy](../subsystems/approval.md) · [ApprovalRequest](../subsystems/approval.md) · [Session](../subsystems/session.md) Source: [`packages/interaction/user-approval/src/index.ts:193`](../../packages/interaction/user-approval/src/index.ts) @@ -300,7 +300,7 @@ abstract run(spec: BashExecSpec): Promise abstract start(spec: BashExecSpec): BashProcess ``` -Types: [BashExecRequest](../core-data-structures/bash.md) · [BashExecSpec](../core-data-structures/bash.md) · [BashProcess](../core-data-structures/bash.md) · [BashRunResult](../core-data-structures/bash.md) +Types: [BashExecRequest](../subsystems/bash.md) · [BashExecSpec](../subsystems/bash.md) · [BashProcess](../subsystems/bash.md) · [BashRunResult](../subsystems/bash.md) Source: [`packages/bash/bash/src/index.ts:53`](../../packages/bash/bash/src/index.ts) @@ -331,7 +331,7 @@ collect(execution: ToolExecution): DshEnvironment list(): BashEnvVariableInfo[] ``` -Types: [DshEnvironment](../core-data-structures/subprocess.md) · [ToolExecution](../core-data-structures/tools.md) +Types: [DshEnvironment](../subsystems/subprocess.md) · [ToolExecution](../subsystems/tools.md) Source: [`packages/bash/bash-env/src/index.ts:89`](../../packages/bash/bash-env/src/index.ts) @@ -396,7 +396,7 @@ Registers one `ctx.codeRuntime` implementation. Program, budget, abort, and subs abstract run(request: CodeRunRequest): Promise ``` -Types: [CodeRunRequest](../core-data-structures/code-runtime.md) · [CodeRunResult](../core-data-structures/code-runtime.md) +Types: [CodeRunRequest](../subsystems/code-runtime.md) · [CodeRunResult](../subsystems/code-runtime.md) Source: [`packages/code-runtime/code-runtime/src/index.ts:104`](../../packages/code-runtime/code-runtime/src/index.ts) @@ -449,7 +449,7 @@ find(agent: Agent, name: string): CommandDefinition | undefined async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [CommandDefinition](../core-data-structures/commands.md) · [CommandDescriptor](../core-data-structures/commands.md) +Types: [Agent](../subsystems/core.md) · [CommandDefinition](../subsystems/commands.md) · [CommandDescriptor](../subsystems/commands.md) Source: [`packages/interaction/commands/src/index.ts:305`](../../packages/interaction/commands/src/index.ts) @@ -514,7 +514,7 @@ abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, ): P abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise ``` -Types: [CompactionResult](../core-data-structures/compaction.md) · [CompactionTrigger](../core-data-structures/compaction.md) +Types: [CompactionResult](../subsystems/compaction.md) · [CompactionTrigger](../subsystems/compaction.md) Source: [`packages/compact/compact/src/index.ts:93`](../../packages/compact/compact/src/index.ts) @@ -560,7 +560,7 @@ abstract set(ref: CredentialRef, value: string): Promise abstract unset(ref: CredentialRef): Promise ``` -Types: [CredentialInfo](../core-data-structures/credentials.md) · [CredentialRef](../core-data-structures/credentials.md) · [ResolvedCredential](../core-data-structures/credentials.md) +Types: [CredentialInfo](../subsystems/credentials.md) · [CredentialRef](../subsystems/credentials.md) · [ResolvedCredential](../subsystems/credentials.md) Source: [`packages/credentials/credentials/src/index.ts:77`](../../packages/credentials/credentials/src/index.ts) @@ -719,7 +719,7 @@ abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise ``` -Types: [FsDirEntry](../core-data-structures/filesystem.md) · [FsEditOutcome](../core-data-structures/filesystem.md) · [FsEditRequest](../core-data-structures/filesystem.md) · [FsInfo](../core-data-structures/filesystem.md) · [FsPathInfo](../core-data-structures/filesystem.md) · [FsTarget](../core-data-structures/filesystem.md) · [FsVersion](../core-data-structures/filesystem.md) · [FsWriteIntent](../core-data-structures/filesystem.md) · [FsWriteOutcome](../core-data-structures/filesystem.md) · [SandboxExecutionPolicy](../core-data-structures/sandbox.md) +Types: [FsDirEntry](../subsystems/filesystem.md) · [FsEditOutcome](../subsystems/filesystem.md) · [FsEditRequest](../subsystems/filesystem.md) · [FsInfo](../subsystems/filesystem.md) · [FsPathInfo](../subsystems/filesystem.md) · [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) · [FsWriteIntent](../subsystems/filesystem.md) · [FsWriteOutcome](../subsystems/filesystem.md) · [SandboxExecutionPolicy](../subsystems/sandbox.md) Source: [`packages/fs/fs/src/index.ts:83`](../../packages/fs/fs/src/index.ts) @@ -814,7 +814,7 @@ block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView @Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult ``` -Types: [Agent](../core-data-structures/core.md) · [CreateGoalRequest](../core-data-structures/goal.md) · [CreateGoalResult](../core-data-structures/goal.md) · [EditGoalRequest](../core-data-structures/goal.md) · [GoalBlockReason](../core-data-structures/goal.md) · [GoalRef](../core-data-structures/goal.md) · [GoalView](../core-data-structures/goal.md) +Types: [Agent](../subsystems/core.md) · [CreateGoalRequest](../subsystems/goal.md) · [CreateGoalResult](../subsystems/goal.md) · [EditGoalRequest](../subsystems/goal.md) · [GoalBlockReason](../subsystems/goal.md) · [GoalRef](../subsystems/goal.md) · [GoalView](../subsystems/goal.md) Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts) @@ -1008,7 +1008,7 @@ async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise ``` -Types: [AdapterRegistrationHandle](../core-data-structures/core.md) · [DirectoryRegistrationHandle](../core-data-structures/core.md) · [GenerateOptions](../core-data-structures/core.md) · [LlmAdapter](../core-data-structures/llm-streaming.md) · [LlmCallConfig](../core-data-structures/core.md) · [LlmConfigurableProvider](../core-data-structures/core.md) · [LlmDiscoveredModel](../core-data-structures/core.md) · [LlmModelDiscoveryRequest](../core-data-structures/core.md) · [LlmModelInfo](../core-data-structures/core.md) · [LlmProviderInfo](../core-data-structures/core.md) · [LlmResolvedModelInfo](../core-data-structures/core.md) · [PreparedLlmCall](../core-data-structures/llm-streaming.md) · [ResolvedRetryPolicy](../core-data-structures/llm-streaming.md) · [StreamChunk](../core-data-structures/llm-streaming.md) +Types: [AdapterRegistrationHandle](../subsystems/core.md) · [DirectoryRegistrationHandle](../subsystems/core.md) · [GenerateOptions](../subsystems/core.md) · [LlmAdapter](../subsystems/llm-streaming.md) · [LlmCallConfig](../subsystems/core.md) · [LlmConfigurableProvider](../subsystems/core.md) · [LlmDiscoveredModel](../subsystems/core.md) · [LlmModelDiscoveryRequest](../subsystems/core.md) · [LlmModelInfo](../subsystems/core.md) · [LlmProviderInfo](../subsystems/core.md) · [LlmResolvedModelInfo](../subsystems/core.md) · [PreparedLlmCall](../subsystems/llm-streaming.md) · [ResolvedRetryPolicy](../subsystems/llm-streaming.md) · [StreamChunk](../subsystems/llm-streaming.md) Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) @@ -1060,7 +1060,7 @@ optionOf(name: string): PresetOption set(session: Session, name: string): void ``` -Types: [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) +Types: [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md) Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts) @@ -1096,7 +1096,7 @@ get(agent: Agent): { active: boolean; pending?: boolean } set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' ``` -Types: [Agent](../core-data-structures/core.md) +Types: [Agent](../subsystems/core.md) Source: [`packages/plan/plan-mode/src/index.ts:183`](../../packages/plan/plan-mode/src/index.ts) @@ -1178,7 +1178,7 @@ async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Pr list(owner: Agent): PtySessionSnapshot[] ``` -Types: [Agent](../core-data-structures/core.md) · [PtyBackend](../core-data-structures/pty.md) · [PtyReadRequest](../core-data-structures/pty.md) · [PtyReadResult](../core-data-structures/pty.md) · [PtySendOperation](../core-data-structures/pty.md) · [PtySendRequest](../core-data-structures/pty.md) · [PtySessionId](../core-data-structures/pty.md) · [PtySessionSnapshot](../core-data-structures/pty.md) · [PtySignal](../core-data-structures/pty.md) · [PtySignalResult](../core-data-structures/pty.md) · [PtySpawnRequest](../core-data-structures/pty.md) · [PtySpawnResult](../core-data-structures/pty.md) +Types: [Agent](../subsystems/core.md) · [PtyBackend](../subsystems/pty.md) · [PtyReadRequest](../subsystems/pty.md) · [PtyReadResult](../subsystems/pty.md) · [PtySendOperation](../subsystems/pty.md) · [PtySendRequest](../subsystems/pty.md) · [PtySessionId](../subsystems/pty.md) · [PtySessionSnapshot](../subsystems/pty.md) · [PtySignal](../subsystems/pty.md) · [PtySignalResult](../subsystems/pty.md) · [PtySpawnRequest](../subsystems/pty.md) · [PtySpawnResult](../subsystems/pty.md) Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts) @@ -1201,7 +1201,7 @@ Abstract process-sandbox service. confine must return enforcing argv or fail clo abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv ``` -Types: [ConfinedArgv](../core-data-structures/sandbox.md) · [SandboxPolicy](../core-data-structures/sandbox.md) +Types: [ConfinedArgv](../subsystems/sandbox.md) · [SandboxPolicy](../subsystems/sandbox.md) Source: [`packages/sandbox/sandbox/src/index.ts:148`](../../packages/sandbox/sandbox/src/index.ts) @@ -1229,7 +1229,7 @@ resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy overrideOf(session: Session): SandboxMode | undefined ``` -Types: [SandboxExecutionPolicy](../core-data-structures/sandbox.md) · [SandboxMode](../core-data-structures/sandbox.md) · [SandboxPolicyRequest](../core-data-structures/sandbox.md) · [Session](../core-data-structures/session.md) +Types: [SandboxExecutionPolicy](../subsystems/sandbox.md) · [SandboxMode](../subsystems/sandbox.md) · [SandboxPolicyRequest](../subsystems/sandbox.md) · [Session](../subsystems/session.md) Source: [`packages/sandbox/sandbox-policy/src/index.ts:91`](../../packages/sandbox/sandbox-policy/src/index.ts) @@ -1350,7 +1350,7 @@ abstract list(signal?: AbortSignal): Promise abstract listSnapshots(signal?: AbortSignal): Promise ``` -Types: [SessionEvent](../core-data-structures/core.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) · [SessionInspection](../core-data-structures/persistence.md) · [SessionLocation](../core-data-structures/persistence.md) · [SessionPersistenceSnapshot](../core-data-structures/persistence.md) · [SessionPreparation](../core-data-structures/persistence.md) +Types: [SessionEvent](../subsystems/core.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) · [SessionInspection](../subsystems/persistence.md) · [SessionLocation](../subsystems/persistence.md) · [SessionPersistenceSnapshot](../subsystems/persistence.md) · [SessionPreparation](../subsystems/persistence.md) Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) @@ -1398,7 +1398,7 @@ async write(session: Session): Promise async coldSnapshot(id: SessionId, signal?: AbortSignal): Promise ``` -Types: [Session](../core-data-structures/session.md) · [SessionHeader](../core-data-structures/persistence.md) · [SessionId](../core-data-structures/core.md) +Types: [Session](../subsystems/session.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../packages/session/session-projection-cache/src/index.ts) @@ -1506,7 +1506,7 @@ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` -Types: [Session](../core-data-structures/session.md) · [SessionEvent](../core-data-structures/core.md) +Types: [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md) Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) @@ -1633,7 +1633,7 @@ async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promi async readEvent(request: SessionEventReadRequest, signal?: AbortSignal): Promise ``` -Types: [SessionEventReadRequest](../core-data-structures/session-query.md) · [SessionEventRecord](../core-data-structures/session-query.md) · [SessionEventResultFilter](../core-data-structures/session-query.md) · [SessionEventSearchDocument](../core-data-structures/session-query.md) · [SessionEventSearchPage](../core-data-structures/session-query.md) · [SessionEventSearchRequest](../core-data-structures/session-query.md) · [SessionEventTraceObservation](../core-data-structures/session-query.md) · [SessionEventTraceRequest](../core-data-structures/session-query.md) · [SessionEventWindow](../core-data-structures/session-query.md) · [SessionId](../core-data-structures/core.md) · [SessionLineageTrace](../core-data-structures/session-query.md) · [SessionLogSnapshot](../core-data-structures/session-query.md) · [SessionRecord](../core-data-structures/session-query.md) · [SessionResultFilter](../core-data-structures/session-query.md) · [SessionSearchExecContext](../core-data-structures/session-query.md) · [SessionSearchHit](../core-data-structures/session-query.md) · [SessionSearchPage](../core-data-structures/session-query.md) · [SessionSearchRequest](../core-data-structures/session-query.md) · [SessionSurfaceSnapshot](../core-data-structures/session-query.md) · [SessionTitleObservation](../core-data-structures/session-query.md) · [SessionTitleObservationResult](../core-data-structures/session-query.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md) +Types: [SessionEventReadRequest](../subsystems/session-query.md) · [SessionEventRecord](../subsystems/session-query.md) · [SessionEventResultFilter](../subsystems/session-query.md) · [SessionEventSearchDocument](../subsystems/session-query.md) · [SessionEventSearchPage](../subsystems/session-query.md) · [SessionEventSearchRequest](../subsystems/session-query.md) · [SessionEventTraceObservation](../subsystems/session-query.md) · [SessionEventTraceRequest](../subsystems/session-query.md) · [SessionEventWindow](../subsystems/session-query.md) · [SessionId](../subsystems/core.md) · [SessionLineageTrace](../subsystems/session-query.md) · [SessionLogSnapshot](../subsystems/session-query.md) · [SessionRecord](../subsystems/session-query.md) · [SessionResultFilter](../subsystems/session-query.md) · [SessionSearchExecContext](../subsystems/session-query.md) · [SessionSearchHit](../subsystems/session-query.md) · [SessionSearchPage](../subsystems/session-query.md) · [SessionSearchRequest](../subsystems/session-query.md) · [SessionSurfaceSnapshot](../subsystems/session-query.md) · [SessionTitleObservation](../subsystems/session-query.md) · [SessionTitleObservationResult](../subsystems/session-query.md) · [SessionTitleSnapshot](../subsystems/session-title.md) Source: [`packages/session-query/session-query/src/index.ts:81`](../../packages/session-query/session-query/src/index.ts) @@ -1663,7 +1663,7 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [PreparedReferencedMessage](../core-data-structures/session-reference.md) · [SessionReferenceCandidate](../core-data-structures/session-reference.md) · [SessionReferenceInput](../core-data-structures/session-reference.md) +Types: [Agent](../subsystems/core.md) · [ContentBlock](../subsystems/core.md) · [PreparedReferencedMessage](../subsystems/session-reference.md) · [SessionReferenceCandidate](../subsystems/session-reference.md) · [SessionReferenceInput](../subsystems/session-reference.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) @@ -1797,7 +1797,7 @@ list(): Session[] fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session ``` -Types: [CreateSessionOptions](../core-data-structures/persistence.md) · [PrepareSessionOptions](../core-data-structures/persistence.md) · [Session](../core-data-structures/session.md) · [SessionId](../core-data-structures/core.md) +Types: [CreateSessionOptions](../subsystems/persistence.md) · [PrepareSessionOptions](../subsystems/persistence.md) · [Session](../subsystems/session.md) · [SessionId](../subsystems/core.md) Source: [`packages/core/session/src/index.ts:807`](../../packages/core/session/src/index.ts) @@ -1844,7 +1844,7 @@ async refresh(session: Session, signal?: AbortSignal): Promise Promise ``` -Types: [Session](../core-data-structures/session.md) · [SessionTitleProvider](../core-data-structures/session-title.md) · [SessionTitleSnapshot](../core-data-structures/session-title.md) +Types: [Session](../subsystems/session.md) · [SessionTitleProvider](../subsystems/session-title.md) · [SessionTitleSnapshot](../subsystems/session-title.md) Source: [`packages/session/session-title/src/index.ts:261`](../../packages/session/session-title/src/index.ts) @@ -1929,7 +1929,7 @@ async replace(ns: SettingsNamespace, section: object, expectedRevision?: number) async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise ``` -Types: [SettingsDescribeOptions](../core-data-structures/settings.md) · [SettingsDescriptor](../core-data-structures/settings.md) · [SettingsNamespace](../core-data-structures/settings.md) · [SettingsPathOp](../core-data-structures/settings.md) · [SettingsRegisterOptions](../core-data-structures/settings.md) · [SettingsScope](../core-data-structures/settings.md) +Types: [SettingsDescribeOptions](../subsystems/settings.md) · [SettingsDescriptor](../subsystems/settings.md) · [SettingsNamespace](../subsystems/settings.md) · [SettingsPathOp](../subsystems/settings.md) · [SettingsRegisterOptions](../subsystems/settings.md) · [SettingsScope](../subsystems/settings.md) Source: [`packages/settings/settings/src/index.ts:387`](../../packages/settings/settings/src/index.ts) @@ -1987,7 +1987,7 @@ async snapshot(options: SkillLookupOptions = {}): Promise async get(name: string, options: SkillLookupOptions = {}): Promise ``` -Types: [SkillCatalogSnapshot](../core-data-structures/skills.md) · [SkillDefinition](../core-data-structures/skills.md) · [SkillLookupOptions](../core-data-structures/skills.md) · [SkillProvider](../core-data-structures/skills.md) · [SkillProviderControl](../core-data-structures/skills.md) · [SkillRegistration](../core-data-structures/skills.md) · [SkillSummary](../core-data-structures/skills.md) +Types: [SkillCatalogSnapshot](../subsystems/skills.md) · [SkillDefinition](../subsystems/skills.md) · [SkillLookupOptions](../subsystems/skills.md) · [SkillProvider](../subsystems/skills.md) · [SkillProviderControl](../subsystems/skills.md) · [SkillRegistration](../subsystems/skills.md) · [SkillSummary](../subsystems/skills.md) Source: [`packages/skill/skill/src/index.ts:304`](../../packages/skill/skill/src/index.ts) @@ -2010,7 +2010,7 @@ Semantics every implementation must honor: abstract saveText(input: SaveTextSpill): Promise ``` -Types: [SaveTextSpill](../core-data-structures/spill.md) · [SpillRef](../core-data-structures/spill.md) +Types: [SaveTextSpill](../subsystems/spill.md) · [SpillRef](../subsystems/spill.md) Source: [`packages/spill/spill/src/index.ts:45`](../../packages/spill/spill/src/index.ts) @@ -2248,7 +2248,7 @@ list(): string[] async start(name: string, request: SubagentStartRequest): Promise ``` -Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableSetupContribution](../core-data-structures/subagent.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [MessageId](../core-data-structures/core.md) · [SessionId](../core-data-structures/core.md) · [SubagentDescendantListEntry](../core-data-structures/subagent.md) · [SubagentFollowupOptions](../core-data-structures/subagent.md) · [SubagentInterruptAuthority](../core-data-structures/subagent.md) · [SubagentListEntry](../core-data-structures/subagent.md) · [SubagentProvider](../core-data-structures/subagent.md) · [SubagentReportOptions](../core-data-structures/subagent.md) · [SubagentRun](../core-data-structures/subagent.md) · [SubagentStartRequest](../core-data-structures/subagent.md) +Types: [Agent](../subsystems/core.md) · [ContentBlock](../subsystems/core.md) · [ContinuableSetupContribution](../subsystems/subagent.md) · [ContinuableStart](../subsystems/subagent.md) · [ContinuableStartSpec](../subsystems/subagent.md) · [MessageId](../subsystems/core.md) · [SessionId](../subsystems/core.md) · [SubagentDescendantListEntry](../subsystems/subagent.md) · [SubagentFollowupOptions](../subsystems/subagent.md) · [SubagentInterruptAuthority](../subsystems/subagent.md) · [SubagentListEntry](../subsystems/subagent.md) · [SubagentProvider](../subsystems/subagent.md) · [SubagentReportOptions](../subsystems/subagent.md) · [SubagentRun](../subsystems/subagent.md) · [SubagentStartRequest](../subsystems/subagent.md) Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) @@ -2297,7 +2297,7 @@ abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise ``` -Types: [SubprocessHandle](../core-data-structures/subprocess.md) · [SubprocessSpawnSpec](../core-data-structures/subprocess.md) · [SubprocessTerminalHandle](../core-data-structures/subprocess.md) · [SubprocessTerminalSpawnSpec](../core-data-structures/subprocess.md) +Types: [SubprocessHandle](../subsystems/subprocess.md) · [SubprocessSpawnSpec](../subsystems/subprocess.md) · [SubprocessTerminalHandle](../subsystems/subprocess.md) · [SubprocessTerminalSpawnSpec](../subsystems/subprocess.md) Source: [`packages/subprocess/subprocess/src/index.ts:102`](../../packages/subprocess/subprocess/src/index.ts) @@ -2353,7 +2353,7 @@ variable(name: string, provider: (context: AssembleContext) => string | undefine async assemble(context: AssembleContext = {}): Promise ``` -Types: [AssembleContext](../core-data-structures/system-prompt.md) · [PromptContext](../core-data-structures/system-prompt.md) · [PromptSection](../core-data-structures/system-prompt.md) · [ToolProviderResult](../core-data-structures/system-prompt.md) +Types: [AssembleContext](../subsystems/system-prompt.md) · [PromptContext](../subsystems/system-prompt.md) · [PromptSection](../subsystems/system-prompt.md) · [ToolProviderResult](../subsystems/system-prompt.md) Source: [`packages/core/system-prompt/src/index.ts:314`](../../packages/core/system-prompt/src/index.ts) @@ -2448,7 +2448,7 @@ abstract onTaskDone(listener: TaskDoneListener): () => void abstract attachSurface(name: string): () => void ``` -Types: [Agent](../core-data-structures/core.md) · [TaskDoneListener](../core-data-structures/tasks.md) · [TaskId](../core-data-structures/tasks.md) · [TaskRead](../core-data-structures/tasks.md) · [TaskSnapshot](../core-data-structures/tasks.md) · [TaskStart](../core-data-structures/tasks.md) +Types: [Agent](../subsystems/core.md) · [TaskDoneListener](../subsystems/tasks.md) · [TaskId](../subsystems/tasks.md) · [TaskRead](../subsystems/tasks.md) · [TaskSnapshot](../subsystems/tasks.md) · [TaskStart](../subsystems/tasks.md) Source: [`packages/tasks/tasks/src/index.ts:50`](../../packages/tasks/tasks/src/index.ts) @@ -2507,7 +2507,7 @@ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement estimateMessage(message: Message): number ``` -Types: [EpochHeader](../core-data-structures/session.md) · [Message](../core-data-structures/core.md) · [Session](../core-data-structures/session.md) · [TokenMeasurement](../core-data-structures/token-meter.md) +Types: [EpochHeader](../subsystems/session.md) · [Message](../subsystems/core.md) · [Session](../subsystems/session.md) · [TokenMeasurement](../subsystems/token-meter.md) Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) @@ -2547,7 +2547,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null pruneSession(session: Session): PruneResult ``` -Types: [ContentBlock](../core-data-structures/core.md) · [PruneResult](../core-data-structures/compaction.md) · [Session](../core-data-structures/session.md) +Types: [ContentBlock](../subsystems/core.md) · [PruneResult](../subsystems/compaction.md) · [Session](../subsystems/session.md) Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) @@ -2630,7 +2630,7 @@ executionMode(exec: ToolExecutionInput): ToolExecutionMode async execute(exec: ToolExecutionInput): Promise ``` -Types: [ScopeKey](../core-data-structures/scope.md) · [ToolDefinition](../core-data-structures/tools.md) · [ToolExecutionInput](../core-data-structures/tools.md) · [ToolExecutionMode](../core-data-structures/tools.md) · [ToolExecutionResult](../core-data-structures/tools.md) · [ToolGuard](../core-data-structures/tools.md) · [ToolRestriction](../core-data-structures/tools.md) · [ToolSchema](../core-data-structures/tools.md) +Types: [ScopeKey](../subsystems/scope.md) · [ToolDefinition](../subsystems/tools.md) · [ToolExecutionInput](../subsystems/tools.md) · [ToolExecutionMode](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolGuard](../subsystems/tools.md) · [ToolRestriction](../subsystems/tools.md) · [ToolSchema](../subsystems/tools.md) Source: [`packages/core/tools/src/index.ts:739`](../../packages/core/tools/src/index.ts) @@ -2743,7 +2743,7 @@ registerProvider(provider: UserInteractionProvider): () => void async ask(request: AskUserQuestionRequest): Promise ``` -Types: [AskUserQuestionAnswer](../core-data-structures/user-interaction.md) · [AskUserQuestionRequest](../core-data-structures/user-interaction.md) · [UserInteractionProvider](../core-data-structures/user-interaction.md) +Types: [AskUserQuestionAnswer](../subsystems/user-interaction.md) · [AskUserQuestionRequest](../subsystems/user-interaction.md) · [UserInteractionProvider](../subsystems/user-interaction.md) Source: [`packages/interaction/user-interaction/src/index.ts:51`](../../packages/interaction/user-interaction/src/index.ts) @@ -2801,7 +2801,7 @@ async search(request: WebSearchRequest, signal?: AbortSignal): Promise ``` -Types: [WebFetchProvider](../core-data-structures/web.md) · [WebFetchRequest](../core-data-structures/web.md) · [WebFetchResult](../core-data-structures/web.md) · [WebSearchProvider](../core-data-structures/web.md) · [WebSearchRequest](../core-data-structures/web.md) · [WebSearchResult](../core-data-structures/web.md) +Types: [WebFetchProvider](../subsystems/web.md) · [WebFetchRequest](../subsystems/web.md) · [WebFetchResult](../subsystems/web.md) · [WebSearchProvider](../subsystems/web.md) · [WebSearchRequest](../subsystems/web.md) · [WebSearchResult](../subsystems/web.md) Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts) @@ -2819,7 +2819,7 @@ Workflow execution seam. Invalid requests throw before publication; a live run i abstract start(request: WorkflowStartRequest): WorkflowRun ``` -Types: [WorkflowRun](../core-data-structures/workflow.md) · [WorkflowStartRequest](../core-data-structures/workflow.md) +Types: [WorkflowRun](../subsystems/workflow.md) · [WorkflowStartRequest](../subsystems/workflow.md) Source: [`packages/workflow/workflow/src/index.ts:159`](../../packages/workflow/workflow/src/index.ts) @@ -2885,7 +2885,7 @@ archiveSession(sessionId: SessionId): Promise async resolveByPath(path: string): Promise ``` -Types: [SessionId](../core-data-structures/core.md) +Types: [SessionId](../subsystems/core.md) Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts) diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 43f985604b..13c766cc80 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/development.md -development.md: 0032000e298ef49516d618e932258e79cf90e802 -development.zh.md: d681306e31991794519b62c3edbb6ca35ebc228b +development.md: 134109fd2a09dfa36aaf2ccdba14daccf896da60 +development.zh.md: 741a860770ee3b20fe94cb0b4993bb281b605b26 diff --git a/docs/development.md b/docs/development.md index 0032000e29..134109fd2a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -156,10 +156,10 @@ Pick the tag that matches the urgency so anyone scanning the code can tell a rel ### Documenting types verbatim (`ts type-equiv`) -The [core data structures](core-data-structures/core.md) docs paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors: +The [core data structures](subsystems/core.md) docs paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors: ```json -{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } +{ "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` `pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `"projection": "public-api"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change. diff --git a/docs/development.zh.md b/docs/development.zh.md index d681306e31..741a860770 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -156,10 +156,10 @@ pnpm run demo:acp ### 逐字记录类型(`ts type-equiv`) -[核心数据结构](core-data-structures/core.md)文档会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号: +[核心数据结构](subsystems/core.md)文档会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号: ```json -{ "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } +{ "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` `pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。 diff --git a/docs/graph-atlas.md b/docs/graph-atlas.md index e783d5fddf..0c955c9480 100644 --- a/docs/graph-atlas.md +++ b/docs/graph-atlas.md @@ -3,7 +3,7 @@ # Documentation Graph Index -These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [core-data-structures/](core-data-structures/core.md). +These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [subsystems/](subsystems/core.md). The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md). diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index 87ef939730..b9a330f21d 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -14,9 +14,9 @@ 依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-spine-demo`,它的职责是组装整套实体主干。 -> This document covers **behavior**; type shapes live in [core-data-structures/](../core-data-structures/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). +> This document covers **behavior**; type shapes live in [subsystems/](../subsystems/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). -本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各包(package)的对外契约写在相应的 README 中([索引](../../packages/README.md))。 +本文档描述整体行为逻辑;类型定义存放于 [subsystems/](../subsystems/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各包(package)的对外契约写在相应的 README 中([索引](../../packages/README.md))。 ## ② 防御模式规则 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index b238e56399..04f992e536 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -3,11 +3,11 @@ # Session Persistence Event Catalog -Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](core-data-structures/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](core-data-structures/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). +Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md). -The envelope declarations below compose each event's `type`, monotonic `seq`, epoch-ms `time`, `data`, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`), and the whole format is pinned at `SESSION_FORMAT_VERSION = 0` — pre-release, no compatibility implied ([the version stance](core-data-structures/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction. +The envelope declarations below compose each event's `type`, monotonic `seq`, epoch-ms `time`, `data`, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`), and the whole format is pinned at `SESSION_FORMAT_VERSION = 0` — pre-release, no compatibility implied ([the version stance](subsystems/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction. ## Event envelope @@ -123,7 +123,7 @@ Source: [`packages/core/agent/src/types.ts:300`](../packages/core/agent/src/type } ``` -Types: [CallId](core-data-structures/core.md) +Types: [CallId](subsystems/core.md) Source: [`packages/interaction/user-approval/src/index.ts:44`](../packages/interaction/user-approval/src/index.ts) @@ -172,7 +172,7 @@ Source: [`packages/interaction/user-approval/src/index.ts:67`](../packages/inter 'assistant/chunk': { turn: number; step: number; chunk: StreamChunk } ``` -Types: [StreamChunk](core-data-structures/llm-streaming.md) +Types: [StreamChunk](subsystems/llm-streaming.md) Source: [`packages/core/session/src/types.ts:238`](../packages/core/session/src/types.ts) @@ -188,7 +188,7 @@ Source: [`packages/core/session/src/types.ts:238`](../packages/core/session/src/ 'assistant/message': { turn: number; step: number; message: AssistantMessage; usage?: TokenUsage } ``` -Types: [TokenUsage](core-data-structures/llm-streaming.md) +Types: [TokenUsage](subsystems/llm-streaming.md) Source: [`packages/core/session/src/types.ts:245`](../packages/core/session/src/types.ts) @@ -328,7 +328,7 @@ Source: [`packages/compact/compact/src/types.ts:19`](../packages/compact/compact ) ``` -Types: [ContentBlock](core-data-structures/core.md) · [TokenUsage](core-data-structures/llm-streaming.md) +Types: [ContentBlock](subsystems/core.md) · [TokenUsage](subsystems/llm-streaming.md) Source: [`packages/compact/compact/src/types.ts:29`](../packages/compact/compact/src/types.ts) @@ -555,7 +555,7 @@ Source: [`packages/core/session/src/types.ts:304`](../packages/core/session/src/ 'session/title': SessionTitleEventData ``` -Types: [SessionTitleEventData](core-data-structures/session-title.md) +Types: [SessionTitleEventData](subsystems/session-title.md) Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/session-title/src/index.ts) @@ -566,7 +566,7 @@ Source: [`packages/session/session-title/src/index.ts:100`](../packages/session/ 'session/title-llm-request': SessionTitleLlmRequestEventData ``` -Types: [SessionTitleLlmRequestEventData](core-data-structures/session-title.md) +Types: [SessionTitleLlmRequestEventData](subsystems/session-title.md) Source: [`packages/session/session-title-llm/src/index.ts:43`](../packages/session/session-title-llm/src/index.ts) @@ -616,7 +616,7 @@ Source: [`packages/subagent/subagent/src/descriptor.ts:37`](../packages/subagent 'todo/write': { todos: TodoItem[] } ``` -Types: [TodoItem](core-data-structures/session.md) +Types: [TodoItem](subsystems/session.md) Source: [`packages/core/session/src/types.ts:271`](../packages/core/session/src/types.ts) @@ -633,7 +633,7 @@ Source: [`packages/core/session/src/types.ts:271`](../packages/core/session/src/ 'tool/call': { turn: number; step: number; callId: CallId; name: string; arguments: string } ``` -Types: [CallId](core-data-structures/core.md) +Types: [CallId](subsystems/core.md) Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/types.ts) @@ -658,7 +658,7 @@ Source: [`packages/core/session/src/types.ts:251`](../packages/core/session/src/ 'tool/code-dispatch': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown; isError: boolean; content: ContentBlock[] } ``` -Types: [CallId](core-data-structures/core.md) · [ContentBlock](core-data-structures/core.md) +Types: [CallId](subsystems/core.md) · [ContentBlock](subsystems/core.md) Source: [`packages/core/tools/src/code-mode.ts:49`](../packages/core/tools/src/code-mode.ts) @@ -681,7 +681,7 @@ Source: [`packages/core/tools/src/code-mode.ts:49`](../packages/core/tools/src/c 'tool/code-dispatch-start': { parentCallId: CallId; subCallId: CallId; name: string; arguments: unknown } ``` -Types: [CallId](core-data-structures/core.md) +Types: [CallId](subsystems/core.md) Source: [`packages/core/tools/src/code-mode.ts:33`](../packages/core/tools/src/code-mode.ts) @@ -726,7 +726,7 @@ Source: [`packages/core/session/src/types.ts:263`](../packages/core/session/src/ 'turn/end': { turn: number; reason: TurnEndReason } ``` -Types: [TurnEndReason](core-data-structures/session.md) +Types: [TurnEndReason](subsystems/session.md) Source: [`packages/core/session/src/types.ts:224`](../packages/core/session/src/types.ts) diff --git a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.i18n.yaml b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.i18n.yaml index 47a2e3fdd7..b54453f3c4 100644 --- a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.i18n.yaml +++ b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md -0004-landlock-partial-notice-misclassified-child-failures.md: db810fdc896f9734d1b581617838d72166f4efc9 -0004-landlock-partial-notice-misclassified-child-failures.zh.md: 4a31fb038c44b036e6a183040947295a47221967 +0004-landlock-partial-notice-misclassified-child-failures.md: ed59fa9d2b04623220a9bcd984fe6becdc619198 +0004-landlock-partial-notice-misclassified-child-failures.zh.md: 5cf3e4422a5b888553e12dfb97b8a26aad65e231 diff --git a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md index db810fdc89..ed59fa9d2b 100644 --- a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +++ b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md @@ -40,7 +40,7 @@ Stderr remains an in-band attribution channel. A confined child can deliberately ## Guardrails added -- [`RunnerFailureRule`](../core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects) carries optional allowed exit codes, case-insensitive per-line fatal signatures, and case-insensitive exact informational-line exclusions. +- [`RunnerFailureRule`](../subsystems/sandbox.md#wrapped-argv-and-classification-dialects) carries optional allowed exit codes, case-insensitive per-line fatal signatures, and case-insensitive exact informational-line exclusions. - [`dsh-sandbox-local`](../../packages/sandbox/sandbox-local/) maps Landlock to exit 125 plus a non-notice `landlock-run:` line while bwrap, Seatbelt, and custom runners remain signature-only. - [`dsh-bash-sandbox`](../../packages/bash/bash-sandbox/) directly spawns the provider argv, so a pre-start rejection uses the spawn-error channel instead of localized shell diagnostics. Settled foreground and background execution share one evidence-returning classifier; fatal evidence outranks denial, and foreground errors report the matched fatal line without changing captured stderr. - [`dsh-tool-fs-search`](../../packages/fs/tool-fs-search/) uses packaged ripgrep through `ctx.subprocess` and remains outside the sandboxed bash seam. diff --git a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md index 4a31fb038c..5cf3e4422a 100644 --- a/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +++ b/docs/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md @@ -40,7 +40,7 @@ stderr 仍是带内归因通道。受限子进程可以故意复现 runner 的 ## 已添加的防护措施 -- [`RunnerFailureRule`](../core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects) 携带可选的允许退出码、不区分大小写的逐行致命签名,以及按不区分大小写的整行精确匹配排除的信息性行。 +- [`RunnerFailureRule`](../subsystems/sandbox.md#wrapped-argv-and-classification-dialects) 携带可选的允许退出码、不区分大小写的逐行致命签名,以及按不区分大小写的整行精确匹配排除的信息性行。 - [`dsh-sandbox-local`](../../packages/sandbox/sandbox-local/) 把 Landlock 映射为退出码 125 加一行非通知的 `landlock-run:` 诊断,而 bwrap、Seatbelt 和自定义 runner 仍仅依据签名。 - [`dsh-bash-sandbox`](../../packages/bash/bash-sandbox/) 直接 spawn 提供方 argv,因此启动前遭拒时使用 spawn 错误通道,而非本地化的 shell 诊断。已结算的前台与后台执行共用一个返回证据的分类器;致命证据优先于拒绝,前台错误会报告匹配到的致命行,同时保持捕获的 stderr 不变。 - [`dsh-tool-fs-search`](../../packages/fs/tool-fs-search/) 通过 `ctx.subprocess` 运行打包的 ripgrep,并继续位于沙箱化 bash seam 之外。 diff --git a/docs/core-data-structures/approval.i18n.yaml b/docs/subsystems/approval.i18n.yaml similarity index 80% rename from docs/core-data-structures/approval.i18n.yaml rename to docs/subsystems/approval.i18n.yaml index 975f631805..a3447601a4 100644 --- a/docs/core-data-structures/approval.i18n.yaml +++ b/docs/subsystems/approval.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/approval.md +# pnpm run verify-translation-pairing --write docs/subsystems/approval.md approval.md: 8f17f98101950e6413d67fb4cce6befc5aede069 approval.zh.md: c19285572b667512427b71d4af214acbc2baf334 diff --git a/docs/core-data-structures/approval.md b/docs/subsystems/approval.md similarity index 100% rename from docs/core-data-structures/approval.md rename to docs/subsystems/approval.md diff --git a/docs/core-data-structures/approval.zh.md b/docs/subsystems/approval.zh.md similarity index 100% rename from docs/core-data-structures/approval.zh.md rename to docs/subsystems/approval.zh.md diff --git a/docs/core-data-structures/bash.i18n.yaml b/docs/subsystems/bash.i18n.yaml similarity index 100% rename from docs/core-data-structures/bash.i18n.yaml rename to docs/subsystems/bash.i18n.yaml diff --git a/docs/core-data-structures/bash.md b/docs/subsystems/bash.md similarity index 100% rename from docs/core-data-structures/bash.md rename to docs/subsystems/bash.md diff --git a/docs/core-data-structures/bash.zh.md b/docs/subsystems/bash.zh.md similarity index 100% rename from docs/core-data-structures/bash.zh.md rename to docs/subsystems/bash.zh.md diff --git a/docs/core-data-structures/code-runtime.i18n.yaml b/docs/subsystems/code-runtime.i18n.yaml similarity index 100% rename from docs/core-data-structures/code-runtime.i18n.yaml rename to docs/subsystems/code-runtime.i18n.yaml diff --git a/docs/core-data-structures/code-runtime.md b/docs/subsystems/code-runtime.md similarity index 100% rename from docs/core-data-structures/code-runtime.md rename to docs/subsystems/code-runtime.md diff --git a/docs/core-data-structures/code-runtime.zh.md b/docs/subsystems/code-runtime.zh.md similarity index 100% rename from docs/core-data-structures/code-runtime.zh.md rename to docs/subsystems/code-runtime.zh.md diff --git a/docs/core-data-structures/commands.i18n.yaml b/docs/subsystems/commands.i18n.yaml similarity index 80% rename from docs/core-data-structures/commands.i18n.yaml rename to docs/subsystems/commands.i18n.yaml index 18df873acc..a741971a9a 100644 --- a/docs/core-data-structures/commands.i18n.yaml +++ b/docs/subsystems/commands.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/commands.md +# pnpm run verify-translation-pairing --write docs/subsystems/commands.md commands.md: 83ce9c2498df58110d9a9c2e7d5163a9df813e1c commands.zh.md: c486f480177c7ff8b35cc846beafac009cea42bb diff --git a/docs/core-data-structures/commands.md b/docs/subsystems/commands.md similarity index 100% rename from docs/core-data-structures/commands.md rename to docs/subsystems/commands.md diff --git a/docs/core-data-structures/commands.zh.md b/docs/subsystems/commands.zh.md similarity index 100% rename from docs/core-data-structures/commands.zh.md rename to docs/subsystems/commands.zh.md diff --git a/docs/core-data-structures/compaction.i18n.yaml b/docs/subsystems/compaction.i18n.yaml similarity index 80% rename from docs/core-data-structures/compaction.i18n.yaml rename to docs/subsystems/compaction.i18n.yaml index 2ad9ea8e8a..7dc00cebfc 100644 --- a/docs/core-data-structures/compaction.i18n.yaml +++ b/docs/subsystems/compaction.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/compaction.md +# pnpm run verify-translation-pairing --write docs/subsystems/compaction.md compaction.md: f1df5b83bd43136af60988dabd9dc68fe32467a2 compaction.zh.md: 52540250d466f81f51cf7c681bb6f1436e29a12d diff --git a/docs/core-data-structures/compaction.md b/docs/subsystems/compaction.md similarity index 100% rename from docs/core-data-structures/compaction.md rename to docs/subsystems/compaction.md diff --git a/docs/core-data-structures/compaction.zh.md b/docs/subsystems/compaction.zh.md similarity index 100% rename from docs/core-data-structures/compaction.zh.md rename to docs/subsystems/compaction.zh.md diff --git a/docs/core-data-structures/core.i18n.yaml b/docs/subsystems/core.i18n.yaml similarity index 80% rename from docs/core-data-structures/core.i18n.yaml rename to docs/subsystems/core.i18n.yaml index ee870f9334..3057ee36ee 100644 --- a/docs/core-data-structures/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/core.md +# pnpm run verify-translation-pairing --write docs/subsystems/core.md core.md: 8f413a7a064ad6f63e0caec31354869e51139020 core.zh.md: d0f02f0cfc2cd30fc67aacf5b17d1daf324295c5 diff --git a/docs/core-data-structures/core.md b/docs/subsystems/core.md similarity index 100% rename from docs/core-data-structures/core.md rename to docs/subsystems/core.md diff --git a/docs/core-data-structures/core.zh.md b/docs/subsystems/core.zh.md similarity index 100% rename from docs/core-data-structures/core.zh.md rename to docs/subsystems/core.zh.md diff --git a/docs/core-data-structures/credentials.i18n.yaml b/docs/subsystems/credentials.i18n.yaml similarity index 80% rename from docs/core-data-structures/credentials.i18n.yaml rename to docs/subsystems/credentials.i18n.yaml index d44275d97e..f7899eaf6e 100644 --- a/docs/core-data-structures/credentials.i18n.yaml +++ b/docs/subsystems/credentials.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/credentials.md +# pnpm run verify-translation-pairing --write docs/subsystems/credentials.md credentials.md: ef74ddeb4346e18f8d5d33488657e5d50f1d754e credentials.zh.md: 09cf374a2346fd93aa834e3372321e6eeece6ed8 diff --git a/docs/core-data-structures/credentials.md b/docs/subsystems/credentials.md similarity index 100% rename from docs/core-data-structures/credentials.md rename to docs/subsystems/credentials.md diff --git a/docs/core-data-structures/credentials.zh.md b/docs/subsystems/credentials.zh.md similarity index 100% rename from docs/core-data-structures/credentials.zh.md rename to docs/subsystems/credentials.zh.md diff --git a/docs/core-data-structures/filesystem.i18n.yaml b/docs/subsystems/filesystem.i18n.yaml similarity index 100% rename from docs/core-data-structures/filesystem.i18n.yaml rename to docs/subsystems/filesystem.i18n.yaml diff --git a/docs/core-data-structures/filesystem.md b/docs/subsystems/filesystem.md similarity index 100% rename from docs/core-data-structures/filesystem.md rename to docs/subsystems/filesystem.md diff --git a/docs/core-data-structures/filesystem.zh.md b/docs/subsystems/filesystem.zh.md similarity index 100% rename from docs/core-data-structures/filesystem.zh.md rename to docs/subsystems/filesystem.zh.md diff --git a/docs/core-data-structures/goal.i18n.yaml b/docs/subsystems/goal.i18n.yaml similarity index 100% rename from docs/core-data-structures/goal.i18n.yaml rename to docs/subsystems/goal.i18n.yaml diff --git a/docs/core-data-structures/goal.md b/docs/subsystems/goal.md similarity index 100% rename from docs/core-data-structures/goal.md rename to docs/subsystems/goal.md diff --git a/docs/core-data-structures/goal.zh.md b/docs/subsystems/goal.zh.md similarity index 100% rename from docs/core-data-structures/goal.zh.md rename to docs/subsystems/goal.zh.md diff --git a/docs/core-data-structures/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml similarity index 80% rename from docs/core-data-structures/llm-streaming.i18n.yaml rename to docs/subsystems/llm-streaming.i18n.yaml index 7168f9bd85..1ff1a1d42b 100644 --- a/docs/core-data-structures/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/llm-streaming.md +# pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md llm-streaming.md: 5c90b3ce4ac65a99997f6ba7ad5deac494b7f772 llm-streaming.zh.md: 7fd0043234cb40d6b21cec6ff101993164785a6e diff --git a/docs/core-data-structures/llm-streaming.md b/docs/subsystems/llm-streaming.md similarity index 100% rename from docs/core-data-structures/llm-streaming.md rename to docs/subsystems/llm-streaming.md diff --git a/docs/core-data-structures/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md similarity index 100% rename from docs/core-data-structures/llm-streaming.zh.md rename to docs/subsystems/llm-streaming.zh.md diff --git a/docs/core-data-structures/lsp.i18n.yaml b/docs/subsystems/lsp.i18n.yaml similarity index 100% rename from docs/core-data-structures/lsp.i18n.yaml rename to docs/subsystems/lsp.i18n.yaml diff --git a/docs/core-data-structures/lsp.md b/docs/subsystems/lsp.md similarity index 100% rename from docs/core-data-structures/lsp.md rename to docs/subsystems/lsp.md diff --git a/docs/core-data-structures/lsp.zh.md b/docs/subsystems/lsp.zh.md similarity index 100% rename from docs/core-data-structures/lsp.zh.md rename to docs/subsystems/lsp.zh.md diff --git a/docs/core-data-structures/persistence.i18n.yaml b/docs/subsystems/persistence.i18n.yaml similarity index 80% rename from docs/core-data-structures/persistence.i18n.yaml rename to docs/subsystems/persistence.i18n.yaml index eccd3f146e..697094b490 100644 --- a/docs/core-data-structures/persistence.i18n.yaml +++ b/docs/subsystems/persistence.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/persistence.md +# pnpm run verify-translation-pairing --write docs/subsystems/persistence.md persistence.md: 177d596d3d01134266f139b47430e1e5064d04d6 persistence.zh.md: 6582c0d7d02d1976d5c3ca3322284098130ed4ad diff --git a/docs/core-data-structures/persistence.md b/docs/subsystems/persistence.md similarity index 100% rename from docs/core-data-structures/persistence.md rename to docs/subsystems/persistence.md diff --git a/docs/core-data-structures/persistence.zh.md b/docs/subsystems/persistence.zh.md similarity index 100% rename from docs/core-data-structures/persistence.zh.md rename to docs/subsystems/persistence.zh.md diff --git a/docs/core-data-structures/pty.i18n.yaml b/docs/subsystems/pty.i18n.yaml similarity index 100% rename from docs/core-data-structures/pty.i18n.yaml rename to docs/subsystems/pty.i18n.yaml diff --git a/docs/core-data-structures/pty.md b/docs/subsystems/pty.md similarity index 100% rename from docs/core-data-structures/pty.md rename to docs/subsystems/pty.md diff --git a/docs/core-data-structures/pty.zh.md b/docs/subsystems/pty.zh.md similarity index 100% rename from docs/core-data-structures/pty.zh.md rename to docs/subsystems/pty.zh.md diff --git a/docs/core-data-structures/sandbox.i18n.yaml b/docs/subsystems/sandbox.i18n.yaml similarity index 100% rename from docs/core-data-structures/sandbox.i18n.yaml rename to docs/subsystems/sandbox.i18n.yaml diff --git a/docs/core-data-structures/sandbox.md b/docs/subsystems/sandbox.md similarity index 100% rename from docs/core-data-structures/sandbox.md rename to docs/subsystems/sandbox.md diff --git a/docs/core-data-structures/sandbox.zh.md b/docs/subsystems/sandbox.zh.md similarity index 100% rename from docs/core-data-structures/sandbox.zh.md rename to docs/subsystems/sandbox.zh.md diff --git a/docs/core-data-structures/scope.i18n.yaml b/docs/subsystems/scope.i18n.yaml similarity index 100% rename from docs/core-data-structures/scope.i18n.yaml rename to docs/subsystems/scope.i18n.yaml diff --git a/docs/core-data-structures/scope.md b/docs/subsystems/scope.md similarity index 100% rename from docs/core-data-structures/scope.md rename to docs/subsystems/scope.md diff --git a/docs/core-data-structures/scope.zh.md b/docs/subsystems/scope.zh.md similarity index 100% rename from docs/core-data-structures/scope.zh.md rename to docs/subsystems/scope.zh.md diff --git a/docs/core-data-structures/session-query.i18n.yaml b/docs/subsystems/session-query.i18n.yaml similarity index 100% rename from docs/core-data-structures/session-query.i18n.yaml rename to docs/subsystems/session-query.i18n.yaml diff --git a/docs/core-data-structures/session-query.md b/docs/subsystems/session-query.md similarity index 100% rename from docs/core-data-structures/session-query.md rename to docs/subsystems/session-query.md diff --git a/docs/core-data-structures/session-query.zh.md b/docs/subsystems/session-query.zh.md similarity index 100% rename from docs/core-data-structures/session-query.zh.md rename to docs/subsystems/session-query.zh.md diff --git a/docs/core-data-structures/session-reference.i18n.yaml b/docs/subsystems/session-reference.i18n.yaml similarity index 79% rename from docs/core-data-structures/session-reference.i18n.yaml rename to docs/subsystems/session-reference.i18n.yaml index 85bbcf8877..f119c67913 100644 --- a/docs/core-data-structures/session-reference.i18n.yaml +++ b/docs/subsystems/session-reference.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/session-reference.md +# pnpm run verify-translation-pairing --write docs/subsystems/session-reference.md session-reference.md: 5375677f6a1748909743ca76d5191cb9e736a40a session-reference.zh.md: 3ff4a1719926bda0a9111482a7778a8c94553370 diff --git a/docs/core-data-structures/session-reference.md b/docs/subsystems/session-reference.md similarity index 100% rename from docs/core-data-structures/session-reference.md rename to docs/subsystems/session-reference.md diff --git a/docs/core-data-structures/session-reference.zh.md b/docs/subsystems/session-reference.zh.md similarity index 100% rename from docs/core-data-structures/session-reference.zh.md rename to docs/subsystems/session-reference.zh.md diff --git a/docs/core-data-structures/session-title.i18n.yaml b/docs/subsystems/session-title.i18n.yaml similarity index 80% rename from docs/core-data-structures/session-title.i18n.yaml rename to docs/subsystems/session-title.i18n.yaml index ed9955472c..c85d4dfac1 100644 --- a/docs/core-data-structures/session-title.i18n.yaml +++ b/docs/subsystems/session-title.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/session-title.md +# pnpm run verify-translation-pairing --write docs/subsystems/session-title.md session-title.md: 7bced67f07df3f00471f02b766922cdc3e4fd29e session-title.zh.md: e8e501d3aafee49395da3d9a8efd4e91a724f09e diff --git a/docs/core-data-structures/session-title.md b/docs/subsystems/session-title.md similarity index 100% rename from docs/core-data-structures/session-title.md rename to docs/subsystems/session-title.md diff --git a/docs/core-data-structures/session-title.zh.md b/docs/subsystems/session-title.zh.md similarity index 100% rename from docs/core-data-structures/session-title.zh.md rename to docs/subsystems/session-title.zh.md diff --git a/docs/core-data-structures/session.i18n.yaml b/docs/subsystems/session.i18n.yaml similarity index 80% rename from docs/core-data-structures/session.i18n.yaml rename to docs/subsystems/session.i18n.yaml index 16e969bd94..dd7135e8d3 100644 --- a/docs/core-data-structures/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/session.md +# pnpm run verify-translation-pairing --write docs/subsystems/session.md session.md: 5d54b03df8ec3345e8bf04702f242e3aacf8ec39 session.zh.md: 5a867cfe302bf02994bfe5a5a704bed55222e8ea diff --git a/docs/core-data-structures/session.md b/docs/subsystems/session.md similarity index 100% rename from docs/core-data-structures/session.md rename to docs/subsystems/session.md diff --git a/docs/core-data-structures/session.zh.md b/docs/subsystems/session.zh.md similarity index 100% rename from docs/core-data-structures/session.zh.md rename to docs/subsystems/session.zh.md diff --git a/docs/core-data-structures/settings.i18n.yaml b/docs/subsystems/settings.i18n.yaml similarity index 80% rename from docs/core-data-structures/settings.i18n.yaml rename to docs/subsystems/settings.i18n.yaml index cf262dc1e9..f4518f4414 100644 --- a/docs/core-data-structures/settings.i18n.yaml +++ b/docs/subsystems/settings.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/settings.md +# pnpm run verify-translation-pairing --write docs/subsystems/settings.md settings.md: bd01c1d28407af9cab26f624a054a010e25a3ddd settings.zh.md: 1cb7f8b507f29f2b6876fd48b4c37284df235e53 diff --git a/docs/core-data-structures/settings.md b/docs/subsystems/settings.md similarity index 100% rename from docs/core-data-structures/settings.md rename to docs/subsystems/settings.md diff --git a/docs/core-data-structures/settings.zh.md b/docs/subsystems/settings.zh.md similarity index 100% rename from docs/core-data-structures/settings.zh.md rename to docs/subsystems/settings.zh.md diff --git a/docs/core-data-structures/skills.i18n.yaml b/docs/subsystems/skills.i18n.yaml similarity index 80% rename from docs/core-data-structures/skills.i18n.yaml rename to docs/subsystems/skills.i18n.yaml index d0ce7b7574..6ab9693cca 100644 --- a/docs/core-data-structures/skills.i18n.yaml +++ b/docs/subsystems/skills.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/skills.md +# pnpm run verify-translation-pairing --write docs/subsystems/skills.md skills.md: d862cbc07135680c2377b4c8c82c80d055344221 skills.zh.md: 3f8c034ec2aa24b4acdbbc1e2ac27717c21649d5 diff --git a/docs/core-data-structures/skills.md b/docs/subsystems/skills.md similarity index 100% rename from docs/core-data-structures/skills.md rename to docs/subsystems/skills.md diff --git a/docs/core-data-structures/skills.zh.md b/docs/subsystems/skills.zh.md similarity index 100% rename from docs/core-data-structures/skills.zh.md rename to docs/subsystems/skills.zh.md diff --git a/docs/core-data-structures/spill.i18n.yaml b/docs/subsystems/spill.i18n.yaml similarity index 100% rename from docs/core-data-structures/spill.i18n.yaml rename to docs/subsystems/spill.i18n.yaml diff --git a/docs/core-data-structures/spill.md b/docs/subsystems/spill.md similarity index 100% rename from docs/core-data-structures/spill.md rename to docs/subsystems/spill.md diff --git a/docs/core-data-structures/spill.zh.md b/docs/subsystems/spill.zh.md similarity index 100% rename from docs/core-data-structures/spill.zh.md rename to docs/subsystems/spill.zh.md diff --git a/docs/core-data-structures/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml similarity index 100% rename from docs/core-data-structures/subagent.i18n.yaml rename to docs/subsystems/subagent.i18n.yaml diff --git a/docs/core-data-structures/subagent.md b/docs/subsystems/subagent.md similarity index 100% rename from docs/core-data-structures/subagent.md rename to docs/subsystems/subagent.md diff --git a/docs/core-data-structures/subagent.zh.md b/docs/subsystems/subagent.zh.md similarity index 100% rename from docs/core-data-structures/subagent.zh.md rename to docs/subsystems/subagent.zh.md diff --git a/docs/core-data-structures/subprocess.i18n.yaml b/docs/subsystems/subprocess.i18n.yaml similarity index 100% rename from docs/core-data-structures/subprocess.i18n.yaml rename to docs/subsystems/subprocess.i18n.yaml diff --git a/docs/core-data-structures/subprocess.md b/docs/subsystems/subprocess.md similarity index 100% rename from docs/core-data-structures/subprocess.md rename to docs/subsystems/subprocess.md diff --git a/docs/core-data-structures/subprocess.zh.md b/docs/subsystems/subprocess.zh.md similarity index 100% rename from docs/core-data-structures/subprocess.zh.md rename to docs/subsystems/subprocess.zh.md diff --git a/docs/core-data-structures/system-prompt.i18n.yaml b/docs/subsystems/system-prompt.i18n.yaml similarity index 80% rename from docs/core-data-structures/system-prompt.i18n.yaml rename to docs/subsystems/system-prompt.i18n.yaml index 8d9cd0897f..5c39e3cbcb 100644 --- a/docs/core-data-structures/system-prompt.i18n.yaml +++ b/docs/subsystems/system-prompt.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/system-prompt.md +# pnpm run verify-translation-pairing --write docs/subsystems/system-prompt.md system-prompt.md: 59193c1881abcadbc8a1778cde92f5a6572eee24 system-prompt.zh.md: 41e45417817895ccf6def70e510eca7422a65e41 diff --git a/docs/core-data-structures/system-prompt.md b/docs/subsystems/system-prompt.md similarity index 100% rename from docs/core-data-structures/system-prompt.md rename to docs/subsystems/system-prompt.md diff --git a/docs/core-data-structures/system-prompt.zh.md b/docs/subsystems/system-prompt.zh.md similarity index 100% rename from docs/core-data-structures/system-prompt.zh.md rename to docs/subsystems/system-prompt.zh.md diff --git a/docs/core-data-structures/tasks.i18n.yaml b/docs/subsystems/tasks.i18n.yaml similarity index 100% rename from docs/core-data-structures/tasks.i18n.yaml rename to docs/subsystems/tasks.i18n.yaml diff --git a/docs/core-data-structures/tasks.md b/docs/subsystems/tasks.md similarity index 100% rename from docs/core-data-structures/tasks.md rename to docs/subsystems/tasks.md diff --git a/docs/core-data-structures/tasks.zh.md b/docs/subsystems/tasks.zh.md similarity index 100% rename from docs/core-data-structures/tasks.zh.md rename to docs/subsystems/tasks.zh.md diff --git a/docs/core-data-structures/token-meter.i18n.yaml b/docs/subsystems/token-meter.i18n.yaml similarity index 100% rename from docs/core-data-structures/token-meter.i18n.yaml rename to docs/subsystems/token-meter.i18n.yaml diff --git a/docs/core-data-structures/token-meter.md b/docs/subsystems/token-meter.md similarity index 100% rename from docs/core-data-structures/token-meter.md rename to docs/subsystems/token-meter.md diff --git a/docs/core-data-structures/token-meter.zh.md b/docs/subsystems/token-meter.zh.md similarity index 100% rename from docs/core-data-structures/token-meter.zh.md rename to docs/subsystems/token-meter.zh.md diff --git a/docs/core-data-structures/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml similarity index 80% rename from docs/core-data-structures/tools.i18n.yaml rename to docs/subsystems/tools.i18n.yaml index cd2add6a82..a30f122038 100644 --- a/docs/core-data-structures/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/tools.md +# pnpm run verify-translation-pairing --write docs/subsystems/tools.md tools.md: 853eed16cff451edcc32bc3aa5c6bc7cabb0f518 tools.zh.md: ec8809f6bebd185404828c5c5879f8eff832ea9a diff --git a/docs/core-data-structures/tools.md b/docs/subsystems/tools.md similarity index 100% rename from docs/core-data-structures/tools.md rename to docs/subsystems/tools.md diff --git a/docs/core-data-structures/tools.zh.md b/docs/subsystems/tools.zh.md similarity index 100% rename from docs/core-data-structures/tools.zh.md rename to docs/subsystems/tools.zh.md diff --git a/docs/core-data-structures/typert.i18n.yaml b/docs/subsystems/typert.i18n.yaml similarity index 100% rename from docs/core-data-structures/typert.i18n.yaml rename to docs/subsystems/typert.i18n.yaml diff --git a/docs/core-data-structures/typert.md b/docs/subsystems/typert.md similarity index 100% rename from docs/core-data-structures/typert.md rename to docs/subsystems/typert.md diff --git a/docs/core-data-structures/typert.zh.md b/docs/subsystems/typert.zh.md similarity index 100% rename from docs/core-data-structures/typert.zh.md rename to docs/subsystems/typert.zh.md diff --git a/docs/core-data-structures/user-interaction.i18n.yaml b/docs/subsystems/user-interaction.i18n.yaml similarity index 79% rename from docs/core-data-structures/user-interaction.i18n.yaml rename to docs/subsystems/user-interaction.i18n.yaml index 3768cfcf7b..80ebb810c8 100644 --- a/docs/core-data-structures/user-interaction.i18n.yaml +++ b/docs/subsystems/user-interaction.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/user-interaction.md +# pnpm run verify-translation-pairing --write docs/subsystems/user-interaction.md user-interaction.md: ec22eb28e9554d6454bf2670f73f2e9b199df014 user-interaction.zh.md: 814f8911e41aecf568f627f76f8239bac4ea719b diff --git a/docs/core-data-structures/user-interaction.md b/docs/subsystems/user-interaction.md similarity index 100% rename from docs/core-data-structures/user-interaction.md rename to docs/subsystems/user-interaction.md diff --git a/docs/core-data-structures/user-interaction.zh.md b/docs/subsystems/user-interaction.zh.md similarity index 100% rename from docs/core-data-structures/user-interaction.zh.md rename to docs/subsystems/user-interaction.zh.md diff --git a/docs/core-data-structures/web.i18n.yaml b/docs/subsystems/web.i18n.yaml similarity index 100% rename from docs/core-data-structures/web.i18n.yaml rename to docs/subsystems/web.i18n.yaml diff --git a/docs/core-data-structures/web.md b/docs/subsystems/web.md similarity index 100% rename from docs/core-data-structures/web.md rename to docs/subsystems/web.md diff --git a/docs/core-data-structures/web.zh.md b/docs/subsystems/web.zh.md similarity index 100% rename from docs/core-data-structures/web.zh.md rename to docs/subsystems/web.zh.md diff --git a/docs/core-data-structures/workflow.i18n.yaml b/docs/subsystems/workflow.i18n.yaml similarity index 100% rename from docs/core-data-structures/workflow.i18n.yaml rename to docs/subsystems/workflow.i18n.yaml diff --git a/docs/core-data-structures/workflow.md b/docs/subsystems/workflow.md similarity index 100% rename from docs/core-data-structures/workflow.md rename to docs/subsystems/workflow.md diff --git a/docs/core-data-structures/workflow.zh.md b/docs/subsystems/workflow.zh.md similarity index 100% rename from docs/core-data-structures/workflow.zh.md rename to docs/subsystems/workflow.zh.md diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 699617eef4..a100469102 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -3,7 +3,7 @@ # Tool Schema Catalog -Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [core-data-structures/](core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered. +Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [subsystems/](subsystems/core.md) (the types those signatures move) — this page is the *tools* the agent is offered. This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator's boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog Agent Note](../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md). diff --git a/packages/bash/bash/README.i18n.yaml b/packages/bash/bash/README.i18n.yaml index 978de64ed1..eee269c121 100644 --- a/packages/bash/bash/README.i18n.yaml +++ b/packages/bash/bash/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bash/bash/README.md -README.md: 88f519a21a0889d6b7649502c51077940c23709f -README.zh.md: 294044692133da8baa57583146352e84c1ff9946 +README.md: 690f4e61740faf2648ecbc7f5995ec0fdaa64aee +README.zh.md: c343e17bfd97eaf17316c034c263b0c71bb8327a diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index 88f519a21a..690f4e6174 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -31,7 +31,7 @@ Implementations subclass `BashExecutor` and implement the abstract methods. Disp `BashExecRequest` (command, workdir?, timeoutMs?, stdoutMaxBytes?, signal?, stdin?, env?, dshEnv?, sandboxPolicy?) resolves to `BashExecSpec` (command, workdir, timeoutMs, stdoutMaxBytes, signal?, stdin?, env?, dshEnv?, sandboxPolicy) before execution. `stdoutMaxBytes` is a trusted foreground-run capture budget for consumers that must parse complete bounded stdout; the model-facing bash tool does not expose it. `sandboxPolicy` is optional on the request and required-but-nullable on the resolved spec: it carries the complete per-call mode and workspace root. The sandbox tool path resolves it from the calling session through `ctx.sandboxPolicy`; a direct sandbox-executor caller falls back to deployment policy, while a non-sandboxing executor carries the field and confines nothing. -The per-session sandbox-mode override vocabulary (the `'sandbox/mode'` event, the `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path) is NOT here — it is policy state shared by every enforcing family, owned by [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/). `run()` returns `BashRunResult`; `start()` returns `BashProcess`, whose incremental read and kill methods are adapted by `dsh-tool-bash` into a generic task registration. A sandboxing executor stamps `BashSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [core-data-structures/bash.md](../../../docs/core-data-structures/bash.md). +The per-session sandbox-mode override vocabulary (the `'sandbox/mode'` event, the `effectiveSandboxMode(events)` fold, and the `setSandboxMode(session, mode)` write path) is NOT here — it is policy state shared by every enforcing family, owned by [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/). `run()` returns `BashRunResult`; `start()` returns `BashProcess`, whose incremental read and kill methods are adapted by `dsh-tool-bash` into a generic task registration. A sandboxing executor stamps `BashSandboxInfo` on foreground results and settled process handles. See `src/types.ts` and [subsystems/bash.md](../../../docs/subsystems/bash.md). `stdin` and ordinary `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload and `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` values. `dshEnv` is a separate trusted overlay restricted by type to managed keys; the exported `DSH_ENV_PREFIX` is the single source for that namespace, its `DshEnvironmentKey` template type, executor scrubbing, registry validation, derived built-in names, and model guidance. Model bash uses the current snapshot collected by `ctx.bashEnv`. Implementations remove inherited managed keys, then merge `dshEnv` after ordinary `env`, so an omitted current fact cannot fall back to stale ambient state and an `env` entry cannot displace a managed value. The model-facing tool exposes none of these as parameters. All three remain optional on the resolved spec; absent means no input/overlay. See [the bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [the session environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md). diff --git a/packages/bash/bash/README.zh.md b/packages/bash/bash/README.zh.md index 2940446921..c343e17bfd 100644 --- a/packages/bash/bash/README.zh.md +++ b/packages/bash/bash/README.zh.md @@ -31,7 +31,7 @@ `BashExecRequest`(command、workdir?、timeoutMs?、stdoutMaxBytes?、signal?、stdin?、env?、dshEnv?、sandboxPolicy?)在执行前解析为 `BashExecSpec`(command、workdir、timeoutMs、stdoutMaxBytes、signal?、stdin?、env?、dshEnv?、sandboxPolicy)。`stdoutMaxBytes` 是受信任前台运行的捕获预算,用于必须解析完整有界 stdout 的消费方;面向模型的 bash 工具不公开该字段。`sandboxPolicy` 在请求上可选,在已解析 spec 上必填但可为 null:它携带完整的每次调用模式与工作区根目录。沙箱工具路径通过 `ctx.sandboxPolicy` 从调用会话解析它;沙箱执行器的直接调用方回退到部署策略,非沙箱执行器则携带该字段但不作限制。 -每会话沙箱模式覆盖词汇(`'sandbox/mode'` 事件、`effectiveSandboxMode(events)` fold 以及 `setSandboxMode(session, mode)` 写入路径)不位于此处。它是所有强制执行家族共享的策略状态,属于 [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/)。`run()` 返回 `BashRunResult`;`start()` 返回 `BashProcess`,其增量读取与终止方法由 `dsh-tool-bash` 适配为通用任务注册。沙箱执行器会在前台结果与已结算进程句柄上标记 `BashSandboxInfo`。详见 `src/types.ts` 与 [core-data-structures/bash.md](../../../docs/core-data-structures/bash.md)。 +每会话沙箱模式覆盖词汇(`'sandbox/mode'` 事件、`effectiveSandboxMode(events)` fold 以及 `setSandboxMode(session, mode)` 写入路径)不位于此处。它是所有强制执行家族共享的策略状态,属于 [`@deepseek-ai/dsh-sandbox-policy`](../../sandbox/sandbox-policy/)。`run()` 返回 `BashRunResult`;`start()` 返回 `BashProcess`,其增量读取与终止方法由 `dsh-tool-bash` 适配为通用任务注册。沙箱执行器会在前台结果与已结算进程句柄上标记 `BashSandboxInfo`。详见 `src/types.ts` 与 [subsystems/bash.md](../../../docs/subsystems/bash.md)。 `stdin` 与普通 `env` 由同进程插件(hooks 桥接、原生插件)设置,用于向 hook 命令提供其 JSON payload 和 `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` 值。`dshEnv` 是受类型限制、仅允许受管 key 的独立受信任 overlay;导出的 `DSH_ENV_PREFIX` 是该 namespace、其 `DshEnvironmentKey` 模板类型、执行器清理、注册表验证、派生内置名称与模型指引的统一来源。模型 bash 使用 `ctx.bashEnv` 收集的当前快照。实现会移除继承的受管 key,再在普通 `env` 之后合并 `dshEnv`,因此省略的当前事实不会回退到陈旧环境状态,`env` 条目也无法顶掉受管值。面向模型的工具不将这三者中的任何一个公开为参数。这三者在已解析 spec 上仍然可选;缺失表示没有输入/overlay。详见 [bash-stdin-env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) 与 [会话环境 Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md)。 diff --git a/packages/compact/compact/README.i18n.yaml b/packages/compact/compact/README.i18n.yaml index 1ab398842f..1252d99e0f 100644 --- a/packages/compact/compact/README.i18n.yaml +++ b/packages/compact/compact/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/compact/compact/README.md -README.md: 5703540435b976cfc03987edca28e2b5cd24ae0d -README.zh.md: 434b9bfa3d0d7f21fc262929ac72ff5ab15d5057 +README.md: 2bf39cac91798b1e0fdb679d2a9b674caceffe22 +README.zh.md: 330b5b0cb9d5c2bf3953354112f3488447e71f87 diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index 5703540435..2bf39cac91 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -24,7 +24,7 @@ All three operations are **abstract** — the backend owns trigger policy, reten | `compactNow(agent, signal)` | Explicitly compact one useful balanced older span even below automatic pressure. It synchronously reserves idle turn admission before yielding, writes nothing when no useful span exists, records a standalone `compact/* { turn: null }` attempt before summarization, and awaits its durability checkpoint before release. Expected operational failures use `ManualCompactionError`; cancellation rethrows the exact abort reason. | | `compactRegion(start, end, agent, signal?)` | Forcibly summarize surface nodes `[start, end]` (inclusive seqs) from `agent.session` into a single replacement node whose source is `COMPACT_CHECKPOINT_SOURCE`. **Throws** if a compaction is already in progress, if `start`/`end` aren't surface nodes, or if `start` is positioned after `end` on the surface. The range is a SURFACE-POSITION span, not a numeric seq interval — after a prior replace lands a fresh high-seq summary node at the shadowed range's position, surface order no longer tracks seq order. | -`CompactionResult` keeps the raw summary and bookkeeping-event seqs available to callers alongside the shadowed range and token accounting; its drift-checked shape lives in the [compaction data-structure reference](../../../docs/core-data-structures/compaction.md#compactionresult). +`CompactionResult` keeps the raw summary and bookkeeping-event seqs available to callers alongside the shadowed range and token accounting; its drift-checked shape lives in the [compaction data-structure reference](../../../docs/subsystems/compaction.md#compactionresult). `compactIfNeeded` and `compactNow` take a required `signal`; `compactRegion`'s is optional. A backend that summarizes via `ctx.llm.stream()` **must** forward it into the call's `GenerateOptions.signal`, so an abort or fiber dispose tears down the in-flight summarization. Automatic and explicit-region brackets recover their numeric owner from the currently open turn. Manual brackets require no open turn and stamp `turn: null`. diff --git a/packages/compact/compact/README.zh.md b/packages/compact/compact/README.zh.md index 434b9bfa3d..330b5b0cb9 100644 --- a/packages/compact/compact/README.zh.md +++ b/packages/compact/compact/README.zh.md @@ -24,7 +24,7 @@ | `compactNow(agent, signal)` | 即使未达到自动压力,也显式压缩一段有效、平衡的较早范围。该操作会在让出控制权前同步预留空闲轮次接纳;没有有效范围时不写入任何内容;在摘要前记录独立的 `compact/* { turn: null }` 尝试;释放预留前等待其持久性检查点。预期操作失败使用 `ManualCompactionError`;取消会原样重新抛出 abort 原因。 | | `compactRegion(start, end, agent, signal?)` | 强制将表层节点 `[start, end]`(包含两端 seq)从 `agent.session` 摘要为单个替换节点,其源为 `COMPACT_CHECKPOINT_SOURCE`。如果压缩已在进行、`start`/`end` 不是表层节点,或 `start` 在表层上位于 `end` 之后,则**抛出异常**。该范围是表层位置范围,不是数值 seq 区间:在之前的 replace 将新生成的高 seq 摘要节点放到已遮蔽范围的位置之后,表层顺序不再跟随 seq 顺序。 | -`CompactionResult` 向调用方保留原始摘要与记录操作过程的事件 seq,同时保留已遮蔽范围与 token 计量;其结构由漂移检查保障,定义见 [压缩数据结构参考](../../../docs/core-data-structures/compaction.md#compactionresult)。 +`CompactionResult` 向调用方保留原始摘要与记录操作过程的事件 seq,同时保留已遮蔽范围与 token 计量;其结构由漂移检查保障,定义见 [压缩数据结构参考](../../../docs/subsystems/compaction.md#compactionresult)。 `compactIfNeeded` 和 `compactNow` 必须传入 `signal`;`compactRegion` 的该参数可选。通过 `ctx.llm.stream()` 摘要的后端**必须** 将它转发到调用的 `GenerateOptions.signal`,因此 abort 或 fiber dispose(资源释放)会停止进行中的摘要。自动和显式范围标记对会从当前打开的轮次恢复其数字形式归属。手动标记对不要求存在打开的轮次,并标记 `turn: null`。 diff --git a/packages/core/session/README.i18n.yaml b/packages/core/session/README.i18n.yaml index 0b2d50957e..c30b37e549 100644 --- a/packages/core/session/README.i18n.yaml +++ b/packages/core/session/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/session/README.md -README.md: e2c014a1448125b23475d6cdf52c02f10fc54794 -README.zh.md: a4aeead796961bd66a6c7ca1f9a66dffb60eba5d +README.md: 07ee53006b390e5ad3979502b2f1aa24f5a6ce7a +README.zh.md: d4a90de10b92a489f8f575b926d758b45d8f5051 diff --git a/packages/core/session/README.md b/packages/core/session/README.md index e2c014a144..07ee53006b 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -56,7 +56,7 @@ The shared [storage codec](src/chunk-rows.ts) losslessly converts event sequence ### Surface types -This package owns ordered surface projection, replacement validation, replay, and the type guards that distinguish append-origin from replacement events. The [surface type catalog](../../../docs/core-data-structures/session.md#surface-types) owns the exact shapes and field semantics. A human transcript must project append-origin events rather than `session.surface`, because landed replacements shadow history the reader already saw; model-facing consumers continue to read `session.surface`. +This package owns ordered surface projection, replacement validation, replay, and the type guards that distinguish append-origin from replacement events. The [surface type catalog](../../../docs/subsystems/session.md#surface-types) owns the exact shapes and field semantics. A human transcript must project append-origin events rather than `session.surface`, because landed replacements shadow history the reader already saw; model-facing consumers continue to read `session.surface`. ### Request-header reconstruction (`request-header.ts`) diff --git a/packages/core/session/README.zh.md b/packages/core/session/README.zh.md index a4aeead796..d4a90de10b 100644 --- a/packages/core/session/README.zh.md +++ b/packages/core/session/README.zh.md @@ -56,7 +56,7 @@ ### Surface 类型 -此包拥有有序 surface 投影、替换校验、回放,以及区分追加来源事件与替换事件的类型守卫。[surface 类型目录](../../../docs/core-data-structures/session.md#surface-types)拥有精确形状与字段语义。面向人的 transcript(文本记录)必须投影追加来源事件,而不是 `session.surface`,因为已落地的替换会遮蔽读者已经看到的历史;面向模型的消费方继续读取 `session.surface`。 +此包拥有有序 surface 投影、替换校验、回放,以及区分追加来源事件与替换事件的类型守卫。[surface 类型目录](../../../docs/subsystems/session.md#surface-types)拥有精确形状与字段语义。面向人的 transcript(文本记录)必须投影追加来源事件,而不是 `session.surface`,因为已落地的替换会遮蔽读者已经看到的历史;面向模型的消费方继续读取 `session.surface`。 ### 请求头重建(`request-header.ts`) diff --git a/packages/core/tools/src/index.ts b/packages/core/tools/src/index.ts index b49350c1a3..657de4578e 100644 --- a/packages/core/tools/src/index.ts +++ b/packages/core/tools/src/index.ts @@ -39,7 +39,7 @@ import { renderToolsSdkPy } from './py-types.ts' * the flavor table is checked against too, so any of the three left out is a * typecheck failure. What no check reaches is the prose that names the values * instead of deriving them: the seam's `dsh-code-runtime` README pair, its - * `CodeRuntime.language` JSDoc, and `docs/core-data-structures/code-runtime.md` + * `CodeRuntime.language` JSDoc, and `docs/subsystems/code-runtime.md` * with its zh pair, plus this package's own README pair and the * {@link Config.mode} JSDoc. */ diff --git a/packages/goal/goal/README.i18n.yaml b/packages/goal/goal/README.i18n.yaml index d918caf377..6362a6d1e9 100644 --- a/packages/goal/goal/README.i18n.yaml +++ b/packages/goal/goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/goal/README.md -README.md: fc2a672c11c68ad72437251b087a274e1c4388d3 -README.zh.md: eaaae5b333151b1d936effc593b21cac515471e2 +README.md: fc2fd8791096bcc9077ef7972da86b0e07c6f18e +README.zh.md: 311ec1d03436cc45bbea28df96e66579852058b0 diff --git a/packages/goal/goal/README.md b/packages/goal/goal/README.md index fc2a672c11..fc2fd87910 100644 --- a/packages/goal/goal/README.md +++ b/packages/goal/goal/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Event-sourced same-session goal state. The service retains one current completion objective in an agent's existing session while keeping permission to continue as process-local activation. The [goal-domain Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) owns the design rationale; the [goal type catalog](../../../docs/core-data-structures/goal.md) records the literal data shapes. +Event-sourced same-session goal state. The service retains one current completion objective in an agent's existing session while keeping permission to continue as process-local activation. The [goal-domain Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) owns the design rationale; the [goal type catalog](../../../docs/subsystems/goal.md) records the literal data shapes. ## Config diff --git a/packages/goal/goal/README.zh.md b/packages/goal/goal/README.zh.md index eaaae5b333..311ec1d034 100644 --- a/packages/goal/goal/README.zh.md +++ b/packages/goal/goal/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -事件溯源的同会话目标状态。该服务在 agent(智能体)的现有会话中保留一个当前完成目标,同时将继续执行的权限作为进程本地续行启用状态。[goal 领域 Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) 负责设计理由;[goal 类型目录](../../../docs/core-data-structures/goal.md)记录具体的数据形状。 +事件溯源的同会话目标状态。该服务在 agent(智能体)的现有会话中保留一个当前完成目标,同时将继续执行的权限作为进程本地续行启用状态。[goal 领域 Agent Note](../../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) 负责设计理由;[goal 类型目录](../../../docs/subsystems/goal.md)记录具体的数据形状。 ## 配置 diff --git a/packages/sandbox/sandbox/README.i18n.yaml b/packages/sandbox/sandbox/README.i18n.yaml index 11f2a2ed86..99a5265418 100644 --- a/packages/sandbox/sandbox/README.i18n.yaml +++ b/packages/sandbox/sandbox/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/sandbox/sandbox/README.md -README.md: 1b522b2c72d00bfed89650aa7f22b65a72d26085 -README.zh.md: adccd4421a74ef073ad3ffc3a23bccb0354d99aa +README.md: 8c1c747f6c7178f9c8c1a9827675ffa98f29aa6d +README.zh.md: e43d1918a99fa96688632551a8f861c2c91f9c2a diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index 1b522b2c72..8c1c747f6c 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -4,7 +4,7 @@ English | [中文](README.zh.md) Abstract process-sandbox seam. Owns the `ctx.sandbox` service contract ([`SandboxProvider`](src/index.ts)) and the confinement vocabulary the harness shares: `SandboxMode` (`read-only` / `workspace-write` / `danger-full-access`, file effects only), `SandboxEnforcement` (`full` / `partial`, per kernel ABI), `SandboxExecutionPolicy` (the complete per-call mode + workspace root), `SandboxPolicy` (its confined subset), and the fail-closed `SANDBOX_UNAVAILABLE` error. Interface package of the [capability-seam split](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): depends only on cordis (+ the harness error base), never on a backend. -The contract in one line: `ctx.sandbox.confine(argv, policy)` returns the argv to spawn INSTEAD of your own — wrapped so the process (and everything it spawns) runs confined — plus the selected backend's enforcement completeness, denial dialect (`denialSignatures`), and structured runner-failure evidence (`runnerFailureRules`); when no backend is usable it throws rather than passing the argv through unconfined. The [core type catalog](../../../docs/core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects) owns the exact classifier shape. +The contract in one line: `ctx.sandbox.confine(argv, policy)` returns the argv to spawn INSTEAD of your own — wrapped so the process (and everything it spawns) runs confined — plus the selected backend's enforcement completeness, denial dialect (`denialSignatures`), and structured runner-failure evidence (`runnerFailureRules`); when no backend is usable it throws rather than passing the argv through unconfined. The [core type catalog](../../../docs/subsystems/sandbox.md#wrapped-argv-and-classification-dialects) owns the exact classifier shape. Policy rides the call, not the provider: two consumers may confine under different policies at the same instant (bash under `read-only` while a confined child agent keeps its state directory writable), and an approved escalated retry is just a new call with a wider policy. diff --git a/packages/sandbox/sandbox/README.zh.md b/packages/sandbox/sandbox/README.zh.md index adccd4421a..e43d1918a9 100644 --- a/packages/sandbox/sandbox/README.zh.md +++ b/packages/sandbox/sandbox/README.zh.md @@ -4,7 +4,7 @@ 抽象进程沙箱 seam。负责定义 `ctx.sandbox` 服务契约([`SandboxProvider`](src/index.ts))与 harness 共享的限制词汇:`SandboxMode`(`read-only`/`workspace-write`/`danger-full-access`,仅限文件操作)、`SandboxEnforcement`(`full`/`partial`,针对每种内核 ABI)、`SandboxExecutionPolicy`(每次调用的完整模式及工作区根目录)、`SandboxPolicy`(其中受限制的子集),以及故障时拒绝放行的 `SANDBOX_UNAVAILABLE` 错误。它是[能力 seam 拆分](../../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)的接口包:只依赖 cordis(及 harness 错误基类),绝不依赖后端。 -用一句话概括契约:`ctx.sandbox.confine(argv, policy)` 返回用于 spawn、应当取代调用方原始 argv 的 argv。返回值经过包装,使进程及其派生的所有进程都在限制下运行;还会附带所选后端达到的强制执行完整度、拒绝方言(`denialSignatures`)和结构化 runner 失败证据(`runnerFailureRules`)。没有可用后端时,它会抛出异常,绝不会原样传递 argv 使其不受限制地运行。[核心类型目录](../../../docs/core-data-structures/sandbox.md#wrapped-argv-and-classification-dialects)负责定义分类器的精确结构。 +用一句话概括契约:`ctx.sandbox.confine(argv, policy)` 返回用于 spawn、应当取代调用方原始 argv 的 argv。返回值经过包装,使进程及其派生的所有进程都在限制下运行;还会附带所选后端达到的强制执行完整度、拒绝方言(`denialSignatures`)和结构化 runner 失败证据(`runnerFailureRules`)。没有可用后端时,它会抛出异常,绝不会原样传递 argv 使其不受限制地运行。[核心类型目录](../../../docs/subsystems/sandbox.md#wrapped-argv-and-classification-dialects)负责定义分类器的精确结构。 策略随调用传递,而不属于提供方:两个消费方可以同时按不同策略施加限制(bash 使用 `read-only`,而受限制的子 agent(智能体)保持其状态目录可写);获批的升权重试只是使用更宽策略发起的新调用。 diff --git a/packages/session/session-title/README.i18n.yaml b/packages/session/session-title/README.i18n.yaml index b13c6ce917..f45602ce97 100644 --- a/packages/session/session-title/README.i18n.yaml +++ b/packages/session/session-title/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/session-title/session-title/README.md -README.md: 9a5ec27c36f3411add37ebe231262eb5d205bc9e -README.zh.md: 3960be40e74507279ed2f21927ee8ad4224fe138 +# pnpm run verify-translation-pairing --write packages/session/session-title/README.md +README.md: 13a2a1c298c3abb8c8987b9373edc46a071ce9db +README.zh.md: 9e271c044033ce17b5c8c744fe7a9cf433c7f473 diff --git a/packages/session/session-title/README.md b/packages/session/session-title/README.md index 9a5ec27c36..13a2a1c298 100644 --- a/packages/session/session-title/README.md +++ b/packages/session/session-title/README.md @@ -31,7 +31,7 @@ All limits are required; the library supplies no defaults. A provider supplies a branded stable id, automatic mode (`first-message` or `all-user-messages`), and `generate(request)`. The request carries the live session, all eligible messages through one fixed revision, the current logged main-request route when available, and cancellation. The result identifies a non-empty title, unique ordered source-message seqs from that request, and optional model provenance. The service normalizes and validates the result before it becomes durable. -See the [session-title data structures](../../../docs/core-data-structures/session-title.md) and [implemented decision](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md). +See the [session-title data structures](../../../docs/subsystems/session-title.md) and [implemented decision](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md). ## Model Experience diff --git a/packages/session/session-title/README.zh.md b/packages/session/session-title/README.zh.md index 3960be40e7..9e271c0440 100644 --- a/packages/session/session-title/README.zh.md +++ b/packages/session/session-title/README.zh.md @@ -31,7 +31,7 @@ Fork 出的会话会原样继承种子中的标题事件。首消息节奏不会 提供方会提供带品牌类型的稳定 id、自动模式(`first-message` 或 `all-user-messages`)和 `generate(request)`。请求携带活跃会话、截至一次固定修订的所有符合条件消息、可用时当前已记录的主请求路由,以及取消信号。结果包含非空标题、该请求中唯一且有序的来源消息 seq,以及可选的模型来源信息。服务会在结果持久保存前进行规范化和验证。 -参见[会话标题数据结构](../../../docs/core-data-structures/session-title.md)与[已实现决策](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)。 +参见[会话标题数据结构](../../../docs/subsystems/session-title.md)与[已实现决策](../../../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)。 ## 模型体验 diff --git a/packages/subprocess/subprocess/README.i18n.yaml b/packages/subprocess/subprocess/README.i18n.yaml index e67bf1a87f..86694c3c23 100644 --- a/packages/subprocess/subprocess/README.i18n.yaml +++ b/packages/subprocess/subprocess/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subprocess/subprocess/README.md -README.md: ec4a4e3328a5a600441d2e7983b3844f4ccc5e91 -README.zh.md: 3da79995fad9a2c8f2a6020ae18152a0e4459c71 +README.md: 6e4b9a9c50bc54a4752930ec14269d2c15233a76 +README.zh.md: d756cd9b7117d7ce9a2e79c6469d9fc188c9600b diff --git a/packages/subprocess/subprocess/README.md b/packages/subprocess/subprocess/README.md index ec4a4e3328..6e4b9a9c50 100644 --- a/packages/subprocess/subprocess/README.md +++ b/packages/subprocess/subprocess/README.md @@ -15,7 +15,7 @@ The subprocess seam (`ctx.subprocess`) is the process half of one execution worl - `scrubbedParentEnv()` / `SENSITIVE_ENV_PATTERN` are the one shared scrub definition: ambient credential-shaped and `DSH_*` names are dropped, and explicit `env` merges after the scrub. The local ordinary and terminal spawns both apply it; SDK-managed transports that own their spawn may import it directly. - Disposal of the service terminates all still-running managed processes and awaits their exit. -See the [subprocess data-structure catalog](../../../docs/core-data-structures/subprocess.md) and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md). +See the [subprocess data-structure catalog](../../../docs/subsystems/subprocess.md) and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md). ## Model Experience diff --git a/packages/subprocess/subprocess/README.zh.md b/packages/subprocess/subprocess/README.zh.md index 3da79995fa..d756cd9b71 100644 --- a/packages/subprocess/subprocess/README.zh.md +++ b/packages/subprocess/subprocess/README.zh.md @@ -15,7 +15,7 @@ - `scrubbedParentEnv()` / `SENSITIVE_ENV_PATTERN` 是唯一一份共享的环境清理定义:环境中形似凭据的名称与 `DSH_*` 名称都会被丢弃,显式 `env` 在清除之后合并。本地的普通 spawn 与终端 spawn 都应用该定义;拥有自身 spawn 的 SDK 管理传输可直接导入它。 - 服务自身的 dispose(资源释放)会终止所有仍在运行的受管进程并等待其退出。 -参见[子进程数据结构目录](../../../docs/core-data-structures/subprocess.md)与[seam Agent Note(agent 决策记录)](../../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md)。 +参见[子进程数据结构目录](../../../docs/subsystems/subprocess.md)与[seam Agent Note(agent 决策记录)](../../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md)。 ## 模型体验 diff --git a/packages/tasks/tasks/README.i18n.yaml b/packages/tasks/tasks/README.i18n.yaml index 7ceb8ff432..1d803e486e 100644 --- a/packages/tasks/tasks/README.i18n.yaml +++ b/packages/tasks/tasks/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/tasks/tasks/README.md -README.md: 2f822bad139020f0ebae0165aa4e8893853f635d -README.zh.md: fdc619fbb46267b2ae550c85cb14fcf8a916f638 +README.md: b5a7380e0cb7df5dd20baecea8656db2aad3460a +README.zh.md: f2e4b10d30d81c840f5598141bbe246634bf2884 diff --git a/packages/tasks/tasks/README.md b/packages/tasks/tasks/README.md index 2f822bad13..b5a7380e0c 100644 --- a/packages/tasks/tasks/README.md +++ b/packages/tasks/tasks/README.md @@ -20,7 +20,7 @@ Owned access compares the task's `SessionId` with the caller's. Ids such as `bas Implementations also owe the lifecycle semantics of the contract: registrations outlive producer and control-surface fibers, owner and service disposal cancel live work and await compliant producers, and settlement is first-wins — one terminal record, one round of contained listener notification, released waiters. -See the [task type catalog](../../../docs/core-data-structures/tasks.md), the [runtime Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md), and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md). +See the [task type catalog](../../../docs/subsystems/tasks.md), the [runtime Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md), and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md). ## Model Experience diff --git a/packages/tasks/tasks/README.zh.md b/packages/tasks/tasks/README.zh.md index fdc619fbb4..f2e4b10d30 100644 --- a/packages/tasks/tasks/README.zh.md +++ b/packages/tasks/tasks/README.zh.md @@ -20,7 +20,7 @@ 实现还必须兑现契约的生命周期语义:注册的存续期长于生产方 fiber 与控制表层 fiber,owner 释放和服务释放会取消仍在运行的工作并等待守约的生产方,结算遵循首次结果优先(一条终止记录、一轮异常受到隔离的监听器通知,然后释放等待方)。 -参见[任务类型目录](../../../docs/core-data-structures/tasks.md)、[运行时 Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)和 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md)。 +参见[任务类型目录](../../../docs/subsystems/tasks.md)、[运行时 Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)和 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md)。 ## 模型体验 diff --git a/packages/typert/generator/src/cordis-catalog.ts b/packages/typert/generator/src/cordis-catalog.ts index e5c2c15a00..d62739acdd 100644 --- a/packages/typert/generator/src/cordis-catalog.ts +++ b/packages/typert/generator/src/cordis-catalog.ts @@ -701,7 +701,7 @@ function typeLinks(signature: string, linkedTypePages: Readonly `[${n}](../core-data-structures/${linkedTypePages[n]})`) + const links = [...seen].sort().map(n => `[${n}](../subsystems/${linkedTypePages[n]})`) return `Types: ${links.join(' · ')}` } @@ -756,7 +756,7 @@ export function renderEvents(events: EventEntry[], policy: CordisCatalogPolicy): ...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.` surface is the sibling [services catalog](services.md), and [core-data-structures/](../core-data-structures/core.md) catalogs the *data structures* these signatures move around.', + '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.` surface is the sibling [services catalog](services.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around.', '', GATE_NOTICE, '', @@ -796,7 +796,7 @@ export function renderServices(services: ServiceEntry[], policy: CordisCatalogPo ...BANNER, '# Cordis Services Catalog', '', - 'Every `ctx.` 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 [core-data-structures/](../core-data-structures/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.', + 'Every `ctx.` 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, '', diff --git a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts index d920ea82ca..edf653c3f4 100644 --- a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts @@ -155,7 +155,7 @@ describe.skip('gen-cordis-catalog collectEvents', { timeout: 60_000 }, () => { ' /**\n * Carry linked and foundation types.\n * @param value - the linked value.\n * @param preset - deployment metadata outside the core catalog.\n * @param signal - cancellation.\n * @mode parallel\n */\n \'fix/typed\'(value: Readonly, preset: PresetSpec, signal: AbortSignal): Promise', )) expect(events).toHaveLength(1) - expect(renderEvents(events)).toContain('Types: [SessionEvent](../core-data-structures/core.md)') + expect(renderEvents(events)).toContain('Types: [SessionEvent](../subsystems/core.md)') expect(renderEvents(events)).not.toContain('[PresetSpec]') }) diff --git a/scripts/gen-config-catalog.ts b/scripts/gen-config-catalog.ts index 920688df6d..ebceec916b 100644 --- a/scripts/gen-config-catalog.ts +++ b/scripts/gen-config-catalog.ts @@ -777,7 +777,7 @@ function requiresLine(inject: string[]): string { } /** Render one reference as a link: another plugin's config type → its section, - * a curated core-data-structures name → its page, any other workspace type → + * a curated subsystems name → its page, any other workspace type → * its source file, an external type → named with its module, unlinked. */ function refLink(ref: TypeRef, byName: Map): string { const target = byName.get(ref.specifier) @@ -785,7 +785,7 @@ function refLink(ref: TypeRef, byName: Map): string { return `[\`${ref.alias}\`](#${slug(target.pkg)})` } const page = LINK_MAP[ref.imported] - if (page) return `[\`${ref.alias}\`](core-data-structures/${page})` + if (page) return `[\`${ref.alias}\`](subsystems/${page})` if (target) return `[\`${ref.alias}\`](../${target.entry})` return `\`${ref.alias}\` (\`${ref.specifier}\`)` } @@ -819,7 +819,7 @@ export function render(entries: CatalogEntry[]): string { '', '# Plugin Config Catalog', '', - 'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [core-data-structures/](core-data-structures/core.md) documents the types these declarations reference.', + 'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference.', '', 'This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verified fresh by `pnpm run verify-config-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks use a `ts config-catalog` fence (skipped by doc-typecheck, since a lone declaration referencing imports is not standalone-compilable). The generator also cross-checks the runtime schemastery schema against the pasted declaration — every schema-validated key, nested keys included, must be locatable on the declared config type — so the paste cannot hide a loader-accepted field.', '', diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 302ec7fdd9..d9f3803547 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -18,7 +18,7 @@ const OUT_EVENTS = 'docs/cordis-catalog/events.md' const OUT_SERVICES = 'docs/cordis-catalog/services.md' const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts' -/** One primary core-data-structures page per project type used by a generated signature. */ +/** One primary subsystems page per project type used by a generated signature. */ export const LINK_MAP: Readonly> = { Agent: 'core.md', AgentCancelCause: 'core.md', diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index e6d6acbdf2..4f72b1d953 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -1344,7 +1344,7 @@ function renderIndex(docs: GraphDoc[]): string { const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode' return [ ...generatedHeader('Documentation Graph Index'), - 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [core-data-structures/](core-data-structures/core.md).', + 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [subsystems/](subsystems/core.md).', '', 'The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md).', '', diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 685e4b65ef..800497ddb7 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -31,7 +31,7 @@ const EVENT_ENVELOPE_TYPE_NAMES = [ type EventEnvelopeTypeName = typeof EVENT_ENVELOPE_TYPE_NAMES[number] -/** Primary core-data-structures page for linked payload types. */ +/** Primary subsystems page for linked payload types. */ const LINK_MAP: Record = { CallId: 'core.md', ContentBlock: 'core.md', @@ -330,7 +330,7 @@ function typeLinks(payload: string): string { if (new RegExp(`\\b${name}\\b`).test(payload)) seen.add(name) } if (seen.size === 0) return '' - const links = [...seen].sort().map(n => `[${n}](core-data-structures/${LINK_MAP[n]})`) + const links = [...seen].sort().map(n => `[${n}](subsystems/${LINK_MAP[n]})`) return `Types: ${links.join(' · ')}` } @@ -352,11 +352,11 @@ export function render(events: AnnotatedLogEventEntry[], envelopeTypes: EventEnv '', '# Session Persistence Event Catalog', '', - 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](core-data-structures/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](core-data-structures/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', + 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', '', 'This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md).', '', - 'The envelope declarations below compose each event\'s `type`, monotonic `seq`, epoch-ms `time`, `data`, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`), and the whole format is pinned at `SESSION_FORMAT_VERSION = 0` — pre-release, no compatibility implied ([the version stance](core-data-structures/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction.', + 'The envelope declarations below compose each event\'s `type`, monotonic `seq`, epoch-ms `time`, `data`, and the conditional `surfaceOp`/`sourceEventSeqs` fields. **surface** marks a `SurfaceEventType` member: it produces an LLM message and declares how it joins the surface list. **log-only** marks everything else: a durable, replayable record with no derived-history contribution. Every payload is JSON-serializable (enforced at `Session.append`), and the whole format is pinned at `SESSION_FORMAT_VERSION = 0` — pre-release, no compatibility implied ([the version stance](subsystems/persistence.md)). Scope: the packages in this repo; a downstream plugin can merge further event types, which are outside this catalog by construction.', '', '## Event envelope', '', diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 55ec5eb57f..39ed4d7620 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -608,7 +608,7 @@ export function render(catalog: ToolCatalog): string { '', '# Tool Schema Catalog', '', - 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [core-data-structures/](core-data-structures/core.md) (the types those signatures move) — this page is the *tools* the agent is offered.', + 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [subsystems/](subsystems/core.md) (the types those signatures move) — this page is the *tools* the agent is offered.', '', 'This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator\'s boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog Agent Note](../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md).', '', diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index 89e417558c..ebdce88f6d 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -271,7 +271,7 @@ describe('docsPages locale routes', () => { it('projects translated core-data pages while retaining explicit English fallbacks', () => { const rootPages = docsPages.filter(page => ( - page.locale === 'root' && page.route.startsWith('reference/core-data-structures/') + page.locale === 'root' && page.route.startsWith('reference/subsystems/') )) const translated = rootPages.filter(page => page.contentLocale === 'zh-CN') const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US') @@ -279,9 +279,9 @@ describe('docsPages locale routes', () => { expect(translated).toHaveLength(20) expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true) expect(fallbacks.map(page => page.source).sort()).toEqual([ - 'docs/core-data-structures/commands.md', - 'docs/core-data-structures/goal.md', - 'docs/core-data-structures/pty.md', + 'docs/subsystems/commands.md', + 'docs/subsystems/goal.md', + 'docs/subsystems/pty.md', ]) }) diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json index 40b3f620e3..3d1ca11612 100644 --- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json +++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json @@ -16,11 +16,11 @@ }, { "role": "user", - "content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI shape. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf. Do not copy this structure to other packages; see the [`api-remotes` README](../packages/api/remotes/README.md) for the complete boundary.\n\nThe root build follows the generated dependency order:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\nBoth tsdown passes use the same complete workspace match. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase.\n\nTypeRT runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start TypeRT. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.\n\nStatic analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already owns an explicit dependency on the TypeRT contract pass or the complete build. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate topology, the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership, and the [TypeRT Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.\n\nBusiness services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.\n\nIf a relevant local check consumes built package output, build once first:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it.\n\n### Environment variables\n\nThe real DeepSeek adapter and key-backed agent demos read credentials from the environment or from a gitignored `.env` at the repo root:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when `DEEPSEEK_API_KEY` is not set.\n\n### Git integrations\n\nThe pairing merge driver derives a conflicted `.i18n.yaml` record from the confirmed ancestor, current, and other owner blobs when both language files use Git's default text strategy and merge cleanly. It fails closed on owner conflicts, non-text merge configuration, or invalid records; after an already-stopped merge, run `pnpm run resolve-translation-pairing-conflicts`, which stages every safe pairing record and exits unsuccessfully if other pairing conflicts still need manual work. See the [bilingual documentation contract](i18n/README.md#the-pairing-contract) for the exact boundary.\n\nThe installer probes the exact Node/tsx driver entrypoint before publishing its worktree configuration. If that runtime later becomes unavailable, the Node-independent launcher writes Git's ordinary text result, leaves the sidecar unresolved, and prints the recovery path; restore dependencies and run `pnpm run resolve-translation-pairing-conflicts`, or run `git merge --abort`. If `pre-merge-commit` rejects an otherwise clean merge, Git leaves the complete result staged without a commit; repair the failure and run `git commit`, or abort. The [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract) owns the exact index and `MERGE_HEAD` states.\n\nlefthook is configured in `lefthook.yml` as a fast local checkpoint:\n\n- `pre-commit` verifies staged pairing records against the staged owner blobs, applies formatting-only ESLint fixes, validates the staged files with the project-free `.oxlintrc.staged.json` profile and applies Oxlint's native fixes, regenerates `THIRD_PARTY_NOTICES.md` when a staged file is one of its inputs, checks the staged diff for whitespace errors, and runs the vendor manifest guard.\n- `pre-merge-commit` performs the same index-backed pairing check before Git creates an automatic merge commit.\n- `pre-push` runs `pnpm run typecheck`, which completes the Host lib phase, including generated TypeRT contracts, before the Client TypeScript check.\n\nThe vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code.\n\nApart from the scoped staged-record verification, the hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.\n\nContributors can opt into the comprehensive local gate set with `pnpm run check:all`. The command is independent of the Git hooks and is not an agent instruction.\n\n### CI gates\n\nThe keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory.\n\n### Daily commands\n\nThe root [contributor instructions](../AGENTS.md#commands) summarize common commands, while [`package.json`](../package.json) and [scripts/run-gates.ts](../scripts/run-gates.ts) own the current script and gate inventories. Select the smallest checks that cover the changed surface. Documentation changes use `pnpm run doc-sync`; package-public behavior changes also update the owning README or JSDoc, and built-artifact checks require `pnpm run build` first.\n\n### Demos\n\nThe one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\nThe self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nThe ACP automation server exposes fresh agent sessions over JSON-RPC stdio and also needs `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO markers\n\nUse one of three comment tags to flag known issues in the code, ordered by urgency:\n\n- `FIXME` — an issue that should block a new release. A release should not ship with an open `FIXME` unless reviewers explicitly agree the change can be merged anyway.\n- `TODO` — an issue that should be fixed soon, once we have the resources.\n- `XXX` — an issue that we may fix someday; lowest priority, no commitment.\n\nPick the tag that matches the urgency so anyone scanning the code can tell a release blocker from a someday-maybe.\n\n### Documenting types verbatim (`ts type-equiv`)\n\nThe [core data structures](core-data-structures/core.md) docs paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors:\n\n```json\n{ \"doc\": \"docs/core-data-structures/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `\"projection\": \"public-api\"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change.\n" + "content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI shape. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf. Do not copy this structure to other packages; see the [`api-remotes` README](../packages/api/remotes/README.md) for the complete boundary.\n\nThe root build follows the generated dependency order:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\nBoth tsdown passes use the same complete workspace match. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase.\n\nTypeRT runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start TypeRT. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.\n\nStatic analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already owns an explicit dependency on the TypeRT contract pass or the complete build. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate topology, the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership, and the [TypeRT Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.\n\nBusiness services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.\n\nIf a relevant local check consumes built package output, build once first:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it.\n\n### Environment variables\n\nThe real DeepSeek adapter and key-backed agent demos read credentials from the environment or from a gitignored `.env` at the repo root:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when `DEEPSEEK_API_KEY` is not set.\n\n### Git integrations\n\nThe pairing merge driver derives a conflicted `.i18n.yaml` record from the confirmed ancestor, current, and other owner blobs when both language files use Git's default text strategy and merge cleanly. It fails closed on owner conflicts, non-text merge configuration, or invalid records; after an already-stopped merge, run `pnpm run resolve-translation-pairing-conflicts`, which stages every safe pairing record and exits unsuccessfully if other pairing conflicts still need manual work. See the [bilingual documentation contract](i18n/README.md#the-pairing-contract) for the exact boundary.\n\nThe installer probes the exact Node/tsx driver entrypoint before publishing its worktree configuration. If that runtime later becomes unavailable, the Node-independent launcher writes Git's ordinary text result, leaves the sidecar unresolved, and prints the recovery path; restore dependencies and run `pnpm run resolve-translation-pairing-conflicts`, or run `git merge --abort`. If `pre-merge-commit` rejects an otherwise clean merge, Git leaves the complete result staged without a commit; repair the failure and run `git commit`, or abort. The [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract) owns the exact index and `MERGE_HEAD` states.\n\nlefthook is configured in `lefthook.yml` as a fast local checkpoint:\n\n- `pre-commit` verifies staged pairing records against the staged owner blobs, applies formatting-only ESLint fixes, validates the staged files with the project-free `.oxlintrc.staged.json` profile and applies Oxlint's native fixes, regenerates `THIRD_PARTY_NOTICES.md` when a staged file is one of its inputs, checks the staged diff for whitespace errors, and runs the vendor manifest guard.\n- `pre-merge-commit` performs the same index-backed pairing check before Git creates an automatic merge commit.\n- `pre-push` runs `pnpm run typecheck`, which completes the Host lib phase, including generated TypeRT contracts, before the Client TypeScript check.\n\nThe vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code.\n\nApart from the scoped staged-record verification, the hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.\n\nContributors can opt into the comprehensive local gate set with `pnpm run check:all`. The command is independent of the Git hooks and is not an agent instruction.\n\n### CI gates\n\nThe keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory.\n\n### Daily commands\n\nThe root [contributor instructions](../AGENTS.md#commands) summarize common commands, while [`package.json`](../package.json) and [scripts/run-gates.ts](../scripts/run-gates.ts) own the current script and gate inventories. Select the smallest checks that cover the changed surface. Documentation changes use `pnpm run doc-sync`; package-public behavior changes also update the owning README or JSDoc, and built-artifact checks require `pnpm run build` first.\n\n### Demos\n\nThe one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\nThe self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nThe ACP automation server exposes fresh agent sessions over JSON-RPC stdio and also needs `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO markers\n\nUse one of three comment tags to flag known issues in the code, ordered by urgency:\n\n- `FIXME` — an issue that should block a new release. A release should not ship with an open `FIXME` unless reviewers explicitly agree the change can be merged anyway.\n- `TODO` — an issue that should be fixed soon, once we have the resources.\n- `XXX` — an issue that we may fix someday; lowest priority, no commitment.\n\nPick the tag that matches the urgency so anyone scanning the code can tell a release blocker from a someday-maybe.\n\n### Documenting types verbatim (`ts type-equiv`)\n\nThe [core data structures](subsystems/core.md) docs paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `\"projection\": \"public-api\"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change.\n" }, { "role": "assistant", - "content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 形态。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全契约;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf。不要把该结构推广到其他包;完整边界见 [`api-remotes` README](../packages/api/remotes/README.md)。\n\n根构建按生成依赖排序:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\n两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package,也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。\n\nTypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成契约构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。\n\n静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本以调用它的公共命令或调度器门禁已经显式依赖 TypeRT 契约 pass 或完整构建为前提。双 aggregate 拓扑见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md),tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md),门禁准备契约见 [TypeRT Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md)。\n\n业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。\n\n如果相关的本地检查需要使用构建后的包产物,请先构建一次:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。\n\n### 环境变量\n\n真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 文件读取凭证:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` 可选,默认为公开 API。请勿提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。\n\n### Git 集成\n\n当两种语言的文件都使用 Git 默认文本策略且能干净合并时,配对合并驱动会根据已确认的祖先、当前和另一侧的配对文档 blob,推导出发生冲突的 `.i18n.yaml` 记录。配对文档发生冲突、存在非文本合并配置或记录无效时,它会拒绝处理并保留冲突;如果合并已经因冲突而停止,请运行 `pnpm run resolve-translation-pairing-conflicts`,该命令会暂存每份可安全生成的配对记录;如果其他配对冲突仍需手工处理,则以非零状态退出。确切边界见[双语文档契约](i18n/README.md#the-pairing-contract)。\n\n安装脚本在发布 worktree 配置前,会探测确切的 Node/tsx 驱动入口点。如果该运行时之后变得不可用,不依赖 Node 的启动器会写入 Git 的普通文本合并结果、让伴随文件保持未解决状态,并打印恢复路径;请恢复依赖后运行 `pnpm run resolve-translation-pairing-conflicts`,或运行 `git merge --abort`。如果 `pre-merge-commit` 拒绝原本能干净完成的合并,Git 会把完整结果留在暂存区但不创建提交;请修复失败后运行 `git commit`,或中止合并。确切的索引与 `MERGE_HEAD` 状态由[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract)负责记录。\n\nlefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点:\n\n- `pre-commit` 对照暂存的配对文档 blob 校验暂存的配对记录,应用仅用于格式化的 ESLint 修复,使用不加载项目的 `.oxlintrc.staged.json` 配置验证暂存文件并应用 Oxlint 的原生修复,在暂存文件属于 `THIRD_PARTY_NOTICES.md` 的输入时重新生成该文件,然后检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫;\n- `pre-merge-commit` 在 Git 创建自动合并提交前执行同样以索引为准的配对检查;\n- `pre-push` 运行 `pnpm run typecheck`;该命令会先完成包含 TypeRT 契约生成的完整 Host lib 阶段,再运行 Client TypeScript 检查。\n\nvendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。\n\n除限定范围的暂存记录校验外,这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。\n\n贡献者可以选择运行 `pnpm run check:all`,执行全面的本地门禁集。该命令独立于 Git 钩子,也不是对 agent 的指令。\n\n### CI 门禁\n\nkeyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。\n\n### 日常命令\n\n根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;package 公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。\n\n### 演示\n\n单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\n自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nACP 自动化服务器通过 JSON-RPC stdio 提供全新 agent 会话,同样需要 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO 标记\n\n请使用以下三种注释标签之一标记代码中的已知问题,按紧急程度排序:\n\n- `FIXME`:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 `FIXME`;\n- `TODO`:应当尽快修复的问题,等资源到位即可处理;\n- `XXX`:也许某天会修复的问题,优先级最低,不作承诺。\n\n请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。\n\n### 逐字记录类型(`ts type-equiv`)\n\n[核心数据结构](core-data-structures/core.md)文档会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号:\n\n```json\n{ \"doc\": \"docs/core-data-structures/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `\"projection\": \"public-api\"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。\n" + "content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 形态。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全契约;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf。不要把该结构推广到其他包;完整边界见 [`api-remotes` README](../packages/api/remotes/README.md)。\n\n根构建按生成依赖排序:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\n两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package,也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。\n\nTypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成契约构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。\n\n静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本以调用它的公共命令或调度器门禁已经显式依赖 TypeRT 契约 pass 或完整构建为前提。双 aggregate 拓扑见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md),tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md),门禁准备契约见 [TypeRT Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md)。\n\n业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。\n\n如果相关的本地检查需要使用构建后的包产物,请先构建一次:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。\n\n### 环境变量\n\n真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 文件读取凭证:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` 可选,默认为公开 API。请勿提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。\n\n### Git 集成\n\n当两种语言的文件都使用 Git 默认文本策略且能干净合并时,配对合并驱动会根据已确认的祖先、当前和另一侧的配对文档 blob,推导出发生冲突的 `.i18n.yaml` 记录。配对文档发生冲突、存在非文本合并配置或记录无效时,它会拒绝处理并保留冲突;如果合并已经因冲突而停止,请运行 `pnpm run resolve-translation-pairing-conflicts`,该命令会暂存每份可安全生成的配对记录;如果其他配对冲突仍需手工处理,则以非零状态退出。确切边界见[双语文档契约](i18n/README.md#the-pairing-contract)。\n\n安装脚本在发布 worktree 配置前,会探测确切的 Node/tsx 驱动入口点。如果该运行时之后变得不可用,不依赖 Node 的启动器会写入 Git 的普通文本合并结果、让伴随文件保持未解决状态,并打印恢复路径;请恢复依赖后运行 `pnpm run resolve-translation-pairing-conflicts`,或运行 `git merge --abort`。如果 `pre-merge-commit` 拒绝原本能干净完成的合并,Git 会把完整结果留在暂存区但不创建提交;请修复失败后运行 `git commit`,或中止合并。确切的索引与 `MERGE_HEAD` 状态由[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract)负责记录。\n\nlefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点:\n\n- `pre-commit` 对照暂存的配对文档 blob 校验暂存的配对记录,应用仅用于格式化的 ESLint 修复,使用不加载项目的 `.oxlintrc.staged.json` 配置验证暂存文件并应用 Oxlint 的原生修复,在暂存文件属于 `THIRD_PARTY_NOTICES.md` 的输入时重新生成该文件,然后检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫;\n- `pre-merge-commit` 在 Git 创建自动合并提交前执行同样以索引为准的配对检查;\n- `pre-push` 运行 `pnpm run typecheck`;该命令会先完成包含 TypeRT 契约生成的完整 Host lib 阶段,再运行 Client TypeScript 检查。\n\nvendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。\n\n除限定范围的暂存记录校验外,这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。\n\n贡献者可以选择运行 `pnpm run check:all`,执行全面的本地门禁集。该命令独立于 Git 钩子,也不是对 agent 的指令。\n\n### CI 门禁\n\nkeyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。\n\n### 日常命令\n\n根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;package 公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。\n\n### 演示\n\n单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\n自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nACP 自动化服务器通过 JSON-RPC stdio 提供全新 agent 会话,同样需要 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO 标记\n\n请使用以下三种注释标签之一标记代码中的已知问题,按紧急程度排序:\n\n- `FIXME`:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 `FIXME`;\n- `TODO`:应当尽快修复的问题,等资源到位即可处理;\n- `XXX`:也许某天会修复的问题,优先级最低,不作承诺。\n\n请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。\n\n### 逐字记录类型(`ts type-equiv`)\n\n[核心数据结构](subsystems/core.md)文档会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `\"projection\": \"public-api\"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。\n" }, { "role": "user", diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 9180a3bd9a..56c1b094be 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -2,1566 +2,1566 @@ "comment": "Maps each primary ` ```ts type-equiv ` or ` ```ts public-api ` block (by doc + declared symbol + projection) to the source declaration and original JSDoc it must match. Paired `.zh.md` blocks are byte-identical derivatives checked through their unsuffixed sibling and have no duplicate entry. Omit projection for the complete declaration; use public-api with a ` ```ts public-api ` block for a body-stripped public class declaration. verify-type-equiv.ts enforces a 1:1 correspondence between primary blocks and entries. Add an entry when you add a primary source-equivalence block; remove it when you remove the block.", "entries": [ { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "Branded", "source": "packages/util/brand/src/index.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "AssistantProvenance", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "Message", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "MessageSourceMap", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ContextForm", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ContextSnapshotSection", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ContextFormed", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "FinishReasonMap", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "AdapterRegistrationHandle", "source": "packages/llm/llm/src/index.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmProviderInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmModelInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmModelDiscoveryRequest", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmDiscoveredModel", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmModelContext", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ReasoningEffortId", "source": "packages/llm/llm/src/brand.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmReasoningEffortInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmModelReasoningInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmResolvedModelInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "GenerateOptions", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "ToolSchema", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmCallConfig", "source": "packages/llm/llm/src/call-config.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmCallConfigAdapterDefaults", "source": "packages/llm/llm/src/call-config.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "InboxTarget", "source": "packages/core/agent/src/inbox.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "CancelOptions", "source": "packages/core/agent/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "AgentCancelCause", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "Agent", "source": "packages/core/agent/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "PreStepDecision", "source": "packages/core/agent/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "RequestErrorAction", "source": "packages/core/agent/src/types.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "SessionStartSource", "source": "packages/core/agent/src/types.ts" }, { - "doc": "docs/core-data-structures/scope.md", + "doc": "docs/subsystems/scope.md", "symbol": "ScopeKey", "source": "packages/core/scope/src/index.ts" }, { - "doc": "docs/core-data-structures/scope.md", + "doc": "docs/subsystems/scope.md", "symbol": "Scoped", "source": "packages/core/scope/src/index.ts" }, { - "doc": "docs/core-data-structures/scope.md", + "doc": "docs/subsystems/scope.md", "symbol": "Scope", "source": "packages/core/scope/src/index.ts" }, { - "doc": "docs/core-data-structures/scope.md", + "doc": "docs/subsystems/scope.md", "symbol": "ScopeLayer", "source": "packages/core/scope/src/store.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalRef", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalPhase", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalBlockReason", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalSnapshot", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalView", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalSnapshotChangeMeta", "source": "packages/goal/goal/src/domain.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalClearChangeMeta", "source": "packages/goal/goal/src/domain.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalMessageSource", "source": "packages/goal/goal/src/domain.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "CreateGoalRequest", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "EditGoalRequest", "source": "packages/goal/goal/src/types.ts" }, { - "doc": "docs/core-data-structures/goal.md", + "doc": "docs/subsystems/goal.md", "symbol": "GoalChanged", "source": "packages/goal/goal/src/domain.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "CommandInputDescriptor", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "CommandDefinition", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "CommandInvocation", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "CommandResult", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "CommandDescriptor", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/commands.md", + "doc": "docs/subsystems/commands.md", "symbol": "ParsedCommand", "source": "packages/interaction/commands/src/index.ts" }, { - "doc": "docs/core-data-structures/system-prompt.md", + "doc": "docs/subsystems/system-prompt.md", "symbol": "AssembleContext", "source": "packages/core/system-prompt/src/index.ts" }, { - "doc": "docs/core-data-structures/system-prompt.md", + "doc": "docs/subsystems/system-prompt.md", "symbol": "PromptContext", "source": "packages/core/system-prompt/src/index.ts" }, { - "doc": "docs/core-data-structures/system-prompt.md", + "doc": "docs/subsystems/system-prompt.md", "symbol": "PromptSection", "source": "packages/core/system-prompt/src/index.ts" }, { - "doc": "docs/core-data-structures/system-prompt.md", + "doc": "docs/subsystems/system-prompt.md", "symbol": "ToolProviderResult", "source": "packages/core/system-prompt/src/index.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "StreamChunk", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmFailure", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "TokenUsage", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "AppIdentity", "source": "packages/llm/llm/src/attribution.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "BlockAssembler", "source": "packages/llm/llm/src/assembler.ts", "projection": "public-api" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "PreparedLlmCall", "source": "packages/llm/llm/src/index.ts" }, { - "doc": "docs/core-data-structures/llm-streaming.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmAdapter", "source": "packages/llm/llm/src/index.ts", "projection": "public-api" }, { - "doc": "docs/core-data-structures/token-meter.md", + "doc": "docs/subsystems/token-meter.md", "symbol": "TokenMeasurement", "source": "packages/llm/token-meter/src/types.ts" }, { - "doc": "docs/core-data-structures/token-meter.md", + "doc": "docs/subsystems/token-meter.md", "symbol": "TokenSurfaceNode", "source": "packages/llm/token-meter/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "UserMessage", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SessionEventMap", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "EpochHeader", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "RequestContext", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "TodoItem", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "TurnEndCancelCause", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "TurnEndReasonMap", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SurfaceEventType", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SurfaceOp", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SurfaceIntent", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SessionSurface", "source": "packages/core/session/src/surface.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SurfaceFoldReplacement", "source": "packages/core/session/src/surface.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "SurfaceFoldResult", "source": "packages/core/session/src/surface.ts" }, { - "doc": "docs/core-data-structures/session.md", + "doc": "docs/subsystems/session.md", "symbol": "Session", "source": "packages/core/session/src/index.ts", "projection": "public-api" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionHeader", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "CreateSessionOptions", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "RestoredSessionOptions", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "PrepareSessionOptions", "source": "packages/core/session/src/types.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionPreparationOptions", "source": "packages/core/session/src/preparation.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionPreparation", "source": "packages/core/session/src/preparation.ts", "projection": "public-api" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionInspection", "source": "packages/session/session-persistence/src/index.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionLocation", "source": "packages/session/session-persistence/src/index.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventSurface", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionRecord", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionLogSnapshot", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionSurfaceSnapshot", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionTitleObservation", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionTitleObservationResult", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventRecord", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionLineageNode", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionLineageTrace", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionQueryErrorCode", "source": "packages/session-query/session-query/src/config.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventReadRequest", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventWindow", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventTraceRequest", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventTrace", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventTraceObservation", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleProviderId", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleModelProvenance", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleSource", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleEventData", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleSnapshot", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleLlmRequestEventData", "source": "packages/session/session-title-llm/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleUserMessage", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleAutomaticMode", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleProviderRequest", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleProviderResult", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-title.md", + "doc": "docs/subsystems/session-title.md", "symbol": "SessionTitleProvider", "source": "packages/session/session-title/src/index.ts" }, { - "doc": "docs/core-data-structures/session-reference.md", + "doc": "docs/subsystems/session-reference.md", "symbol": "SessionReferenceInput", "source": "packages/context/session-reference/src/types.ts" }, { - "doc": "docs/core-data-structures/session-reference.md", + "doc": "docs/subsystems/session-reference.md", "symbol": "SessionReferenceCandidate", "source": "packages/context/session-reference/src/types.ts" }, { - "doc": "docs/core-data-structures/session-reference.md", + "doc": "docs/subsystems/session-reference.md", "symbol": "PreparedReferencedMessage", "source": "packages/context/session-reference/src/types.ts" }, { - "doc": "docs/core-data-structures/session-reference.md", + "doc": "docs/subsystems/session-reference.md", "symbol": "SessionReferenceErrorCode", "source": "packages/context/session-reference/src/config.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolOutputDefinition", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolDefinition", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ValueSchemaSpec", "source": "packages/core/tools/src/schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ParameterPropertySpec", "source": "packages/core/tools/src/schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ParameterSchemaSpec", "source": "packages/core/tools/src/schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "InferValue", "source": "packages/core/tools/src/schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "InferArgs", "source": "packages/core/tools/src/schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionToken", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionInput", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecution", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolDispatchExecution", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionMode", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "CodeDispatchLog", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolRunContext", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolGuard", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolRestriction", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolFailure", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionSuccess", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionFailure", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ToolExecutionResult", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "PreToolDecision", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "PostToolDecision", "source": "packages/core/tools/src/index.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "JsonSchemaScalar", "source": "packages/core/tools/src/json-schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "JsonSchemaType", "source": "packages/core/tools/src/json-schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "JsonSchemaNode", "source": "packages/core/tools/src/json-schema.ts" }, { - "doc": "docs/core-data-structures/tools.md", + "doc": "docs/subsystems/tools.md", "symbol": "ObjectJsonSchema", "source": "packages/core/tools/src/json-schema.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionOption", "source": "packages/interaction/user-interaction/src/types.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionIntent", "source": "packages/interaction/user-interaction/src/types.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionItem", "source": "packages/interaction/user-interaction/src/types.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionRequest", "source": "packages/interaction/user-interaction/src/index.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionAnswerItem", "source": "packages/interaction/user-interaction/src/types.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "AskUserQuestionAnswer", "source": "packages/interaction/user-interaction/src/types.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "UserInteractionProvider", "source": "packages/interaction/user-interaction/src/index.ts" }, { - "doc": "docs/core-data-structures/user-interaction.md", + "doc": "docs/subsystems/user-interaction.md", "symbol": "UserInteractionError", "source": "packages/interaction/user-interaction/src/index.ts" }, { - "doc": "docs/core-data-structures/approval.md", + "doc": "docs/subsystems/approval.md", "symbol": "ApprovalRequestId", "source": "packages/interaction/user-approval/src/types.ts" }, { - "doc": "docs/core-data-structures/approval.md", + "doc": "docs/subsystems/approval.md", "symbol": "ApprovalOutcome", "source": "packages/interaction/user-approval/src/types.ts" }, { - "doc": "docs/core-data-structures/approval.md", + "doc": "docs/subsystems/approval.md", "symbol": "ApprovalPolicy", "source": "packages/interaction/user-approval/src/index.ts" }, { - "doc": "docs/core-data-structures/approval.md", + "doc": "docs/subsystems/approval.md", "symbol": "ApprovalRequest", "source": "packages/interaction/user-approval/src/index.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashExecRequest", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashExecSpec", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashRunResult", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashSandboxInfo", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashProcess", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/bash.md", + "doc": "docs/subsystems/bash.md", "symbol": "BashProcessRead", "source": "packages/bash/bash/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskKindMap", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskStart", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskHooks", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskOutcome", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskSnapshot", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/tasks.md", + "doc": "docs/subsystems/tasks.md", "symbol": "TaskRead", "source": "packages/tasks/tasks/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtyWaitReason", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtySessionStatus", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtyBackend", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtyBackendSession", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtySendOperation", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/pty.md", + "doc": "docs/subsystems/pty.md", "symbol": "PtySendResult", "source": "packages/pty/pty/src/types.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "SandboxMode", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "ConfinedSandboxMode", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "SandboxExecutionPolicy", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "SandboxEnforcement", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "SandboxPolicy", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "SandboxPolicyRequest", "source": "packages/sandbox/sandbox-policy/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "RunnerFailureRule", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/sandbox.md", + "doc": "docs/subsystems/sandbox.md", "symbol": "ConfinedArgv", "source": "packages/sandbox/sandbox/src/index.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeJsonValue", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeRunRequest", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeRunResult", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeBindingNamespace", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeBindingErrorClass", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeBindingFunction", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/code-runtime.md", + "doc": "docs/subsystems/code-runtime.md", "symbol": "CodeRunFailure", "source": "packages/code-runtime/code-runtime/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsTarget", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsTargetKey", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsVersion", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsInfo", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsPathInfo", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsDirEntry", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsWriteIntent", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsWriteOutcome", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsEditRequest", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsEditOutcome", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsErrorCode", "source": "packages/fs/fs/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FsPolicyExec", "source": "packages/fs/fs-policy/src/types.ts" }, { - "doc": "docs/core-data-structures/filesystem.md", + "doc": "docs/subsystems/filesystem.md", "symbol": "FileReadOutcome", "source": "packages/fs/tool-fs/src/read-render.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillSource", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillResourceBase", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillInvocationPolicy", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillSummary", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillCatalogSnapshot", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillCandidate", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillDefinition", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillRegistration", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillLookupOptions", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillProviderObservation", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillProvider", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "SkillProviderControl", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/skills.md", + "doc": "docs/subsystems/skills.md", "symbol": "Config", "source": "packages/skill/skill/src/index.ts" }, { - "doc": "docs/core-data-structures/compaction.md", + "doc": "docs/subsystems/compaction.md", "symbol": "CompactionResult", "source": "packages/compact/compact/src/types.ts" }, { - "doc": "docs/core-data-structures/compaction.md", + "doc": "docs/subsystems/compaction.md", "symbol": "CompactionTrigger", "source": "packages/compact/compact/src/index.ts" }, { - "doc": "docs/core-data-structures/compaction.md", + "doc": "docs/subsystems/compaction.md", "symbol": "ManualCompactionErrorCode", "source": "packages/compact/compact/src/index.ts" }, { - "doc": "docs/core-data-structures/compaction.md", + "doc": "docs/subsystems/compaction.md", "symbol": "PrunedEntry", "source": "packages/compact/compact-tool-result-prune/src/types.ts" }, { - "doc": "docs/core-data-structures/compaction.md", + "doc": "docs/subsystems/compaction.md", "symbol": "PruneResult", "source": "packages/compact/compact-tool-result-prune/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentCapabilities", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentStartRequest", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "ResolvedSubagentStartRequest", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "CoordinatorMessageSource", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentReportMessageSource", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentReportDelivery", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentReportOptions", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentFollowupOptions", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentInterruptAuthority", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentDescendantListEntry", "source": "packages/subagent/subagent/src/list-children.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "ContinuableStart", "source": "packages/subagent/subagent/src/continuation.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "ContinuableCreateRequest", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "ContinuableCreateSpec", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentResult", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentStopReasonMap", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentRun", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/subagent.md", + "doc": "docs/subsystems/subagent.md", "symbol": "SubagentProvider", "source": "packages/subagent/subagent/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebSearchRequest", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebSearchResult", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebSearchSource", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebFetchRequest", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebFetchResult", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/web.md", + "doc": "docs/subsystems/web.md", "symbol": "WebFetchBody", "source": "packages/web/web/src/types.ts" }, { - "doc": "docs/core-data-structures/spill.md", + "doc": "docs/subsystems/spill.md", "symbol": "SaveTextSpill", "source": "packages/spill/spill/src/types.ts" }, { - "doc": "docs/core-data-structures/spill.md", + "doc": "docs/subsystems/spill.md", "symbol": "SpillOwner", "source": "packages/spill/spill/src/types.ts" }, { - "doc": "docs/core-data-structures/spill.md", + "doc": "docs/subsystems/spill.md", "symbol": "SpillSource", "source": "packages/spill/spill/src/types.ts" }, { - "doc": "docs/core-data-structures/spill.md", + "doc": "docs/subsystems/spill.md", "symbol": "SpillRef", "source": "packages/spill/spill/src/types.ts" }, { - "doc": "docs/core-data-structures/spill.md", + "doc": "docs/subsystems/spill.md", "symbol": "SpillLocator", "source": "packages/spill/spill/src/types.ts" }, { - "doc": "docs/core-data-structures/workflow.md", + "doc": "docs/subsystems/workflow.md", "symbol": "WorkflowStartRequest", "source": "packages/workflow/workflow/src/types.ts" }, { - "doc": "docs/core-data-structures/workflow.md", + "doc": "docs/subsystems/workflow.md", "symbol": "WorkflowMeta", "source": "packages/workflow/workflow/src/types.ts" }, { - "doc": "docs/core-data-structures/workflow.md", + "doc": "docs/subsystems/workflow.md", "symbol": "WorkflowResult", "source": "packages/workflow/workflow/src/types.ts" }, { - "doc": "docs/core-data-structures/workflow.md", + "doc": "docs/subsystems/workflow.md", "symbol": "WorkflowRun", "source": "packages/workflow/workflow/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspOperation", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspPosition", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspRange", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspQueryRequest", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspProviderQuery", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspLocation", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspHover", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspQueryResult", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspProvider", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/lsp.md", + "doc": "docs/subsystems/lsp.md", "symbol": "LspService", "source": "packages/lsp/lsp/src/types.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionPersistenceRevision", "source": "packages/session/session-persistence/src/revision.ts" }, { - "doc": "docs/core-data-structures/persistence.md", + "doc": "docs/subsystems/persistence.md", "symbol": "SessionPersistenceSnapshot", "source": "packages/session/session-persistence/src/index.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionResultFilter", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventResultFilter", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventSearchDocument", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionSearchCursor", "source": "packages/session-query/session-query/src/cursor.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionSearchRequest", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventSearchRequest", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionSearchPage", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventSearchPage", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionEventSearchHit", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/session-query.md", + "doc": "docs/subsystems/session-query.md", "symbol": "SessionSearchHit", "source": "packages/session-query/session-query/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessSpawnSpec", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessHandle", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessOutputReader", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessOutputRead", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessOutcome", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "DshEnvironmentKey", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "DshEnvironment", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "CollectedOutput", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessStdinMode", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessCollect", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessOutputMode", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessStdio", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/subprocess.md", + "doc": "docs/subsystems/subprocess.md", "symbol": "SubprocessCollectedOutputs", "source": "packages/subprocess/subprocess/src/types.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsNamespace", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsRegisterOptions", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsApplies", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsScope", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsDescriptor", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsUpdateSource", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/credentials.md", + "doc": "docs/subsystems/credentials.md", "symbol": "CredentialRef", "source": "packages/credentials/credentials/src/index.ts" }, { - "doc": "docs/core-data-structures/credentials.md", + "doc": "docs/subsystems/credentials.md", "symbol": "ResolvedCredential", "source": "packages/credentials/credentials/src/index.ts" }, { - "doc": "docs/core-data-structures/credentials.md", + "doc": "docs/subsystems/credentials.md", "symbol": "CredentialInfo", "source": "packages/credentials/credentials/src/index.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsDescribeOptions", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/core.md", + "doc": "docs/subsystems/core.md", "symbol": "LlmConfigurableProvider", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/core-data-structures/settings.md", + "doc": "docs/subsystems/settings.md", "symbol": "SettingsPathOp", "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTLookupMap", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTContextMap", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTLookupDefinition", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTCodec", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "InvocationParameterDescriptor", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "InvocationDescriptor", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTService", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTRemoteNamespaceMap", "source": "packages/typert/type-meta/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "InvokeRemoteRequest", "source": "packages/api/gateway/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypertGatewayErrorCode", "source": "packages/api/gateway/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypertGateway", "source": "packages/api/gateway/src/types.ts" }, { - "doc": "docs/core-data-structures/typert.md", + "doc": "docs/subsystems/typert.md", "symbol": "TypeRTClientRemote", "source": "packages/typert/type-meta/src/types.ts" } diff --git a/website/docs.ts b/website/docs.ts index 8db7951dbb..07f470319b 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -279,13 +279,13 @@ const coreDataReference = pairedPages(([ ['settings.md', '用户设置', 'User settings', 21], ['credentials.md', '用户凭据', 'User credentials', 22], ] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({ - source: `docs/core-data-structures/${file}`, - route: `reference/core-data-structures/${file}`, + source: `docs/subsystems/${file}`, + route: `reference/subsystems/${file}`, label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, section: { root: '数据结构', en: 'Data structures' }, order, - ...(file === 'core.md' ? { sourceAliases: ['docs/core-data-structures'] } : {}), + ...(file === 'core.md' ? { sourceAliases: ['docs/subsystems'] } : {}), }))) const reference = mirroredPages([ @@ -339,8 +339,8 @@ const reference = mirroredPages([ ['pty.md', 'PTY 会话', 'PTY sessions', 8], ['commands.md', '命令', 'Human commands', 17], ] as const).map(([file, rootLabel, enLabel, order]): MirroredPage => ({ - source: `docs/core-data-structures/${file}`, - route: `reference/core-data-structures/${file}`, + source: `docs/subsystems/${file}`, + route: `reference/subsystems/${file}`, contentLocale: 'en-US', label: { root: rootLabel, en: enLabel }, sidebar: { root: 'zh-reference', en: 'en-reference' }, From cf5e53e051bf34cfad9a47789a18dd0fa3224410 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:37:05 +0800 Subject: [PATCH 07/19] docs: fix stale package README claims found while writing subsystem pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dsh-host-webserver: the service is HttpServerService under ctx.httpServer (README said WebServerService/ctx.webServer, matching no symbol in source), and the dev-mode bundle-watch paragraph described machinery that moved to dsh-client-modules/dsh-client-hmr in the webserver rewrite — removed; those packages' READMEs already document the current pipeline. - dsh-workspace: ctx.workspace.touchSession(id) does not exist; the activity-pinning chain was replaced by Workspace.insertSessionBefore (2026-07-25 session-list note) — the bullet now documents the real method. - packages/storage group table: the domain package directory is storage-domain/, not domain/. All three pairs updated on both sides and re-recorded. --- packages/host/webserver/README.i18n.yaml | 4 ++-- packages/host/webserver/README.md | 2 -- packages/host/webserver/README.zh.md | 2 -- packages/workspace/workspace/README.i18n.yaml | 2 +- packages/workspace/workspace/README.md | 1 + 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/host/webserver/README.i18n.yaml b/packages/host/webserver/README.i18n.yaml index 56fd0e7694..bd80f27f3e 100644 --- a/packages/host/webserver/README.i18n.yaml +++ b/packages/host/webserver/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/host/webserver/README.md -README.md: b6dccf2f81c9e2f0b9f53264eafe724edb560f07 -README.zh.md: dbfe420013ed67c48e47048341f020864aeef16a +README.md: 569c3f0c19db2c308beaef35baaf915fd39768cd +README.zh.md: b311ebffa3fb067e684e9e443c2ac02dab917d61 diff --git a/packages/host/webserver/README.md b/packages/host/webserver/README.md index b6dccf2f81..569c3f0c19 100644 --- a/packages/host/webserver/README.md +++ b/packages/host/webserver/README.md @@ -8,8 +8,6 @@ The package knows no harness concepts and serves no files: the `/api` HTTP bridg A listen failure (EADDRINUSE…) throws out of activation and rejects Loader composition with the bind diagnostic; the failed candidate fiber is disposed. An HTTP request whose handling throws (a fallback owner's `decodeURIComponent` on a malformed %-escape, a client dropping mid-body) is answered 400 — or the socket destroyed when headers are already out — and logged as a warning; it never exits the process. An upgrade-handler exception or upgraded-socket transport error is logged as a warning and destroys its socket. Disposal starts `close()` and `closeAllConnections()`, destroys every tracked upgraded socket, and returns only after the HTTP server and those sockets have closed. -In development, the client-plugin registry synchronously captures each built bundle's stat baseline before it returns, then polls those baselines and re-hashes changed content. Each rescan stages its candidate table, graph, and watch map before publishing them, so a baseline failure preserves the prior graph. An immediate rebuild therefore cannot disappear into an asynchronously established watch baseline; a rename window marks the path dirty, retains the last successful baseline, and forces a re-hash when the bundle reappears even with identical metadata. - ## Model Experience None, as the package is a Web carrier between the browser and the HTTP/upgrade routes other plugins register; nothing here reaches a model request. diff --git a/packages/host/webserver/README.zh.md b/packages/host/webserver/README.zh.md index dbfe420013..b311ebffa3 100644 --- a/packages/host/webserver/README.zh.md +++ b/packages/host/webserver/README.zh.md @@ -8,8 +8,6 @@ Web HTTP 与 upgrade route 注册插件(默认导出 `HttpServerService`,配 监听失败(EADDRINUSE……)会从激活过程抛出,以 bind 诊断使 Loader 组合 reject;失败的候选 fiber 会被 dispose(资源释放)。处理 HTTP 请求时抛错(例如 fallback 持有者的 `decodeURIComponent` 收到格式错误的百分号转义,或客户端在请求体传输中途断开)时,服务器会响应 400;若响应头已经发出,则销毁 socket,并记录 warning,但绝不会退出进程。upgrade handler 抛错或升级 socket 出现传输错误时,会记录 warning 并销毁对应 socket。资源释放会启动 `close()` 与 `closeAllConnections()`,销毁所有受跟踪的升级 socket,并仅在 HTTP server 与这些 socket 均已关闭后返回。 -在开发环境中,客户端插件注册表会在返回前同步捕获每个已构建 bundle 的 stat 基线,随后轮询这些基线,并在内容变化后重新计算哈希。每次重新扫描都会先暂存候选表、图和监听 map,再统一发布,因此基线失败会保留先前的图。这样,即时重建不会消失在异步建立的监听基线中;重命名窗口会把路径标记为脏,保留最近一次成功基线,并在 bundle 重新出现时强制重新计算哈希,即使其元数据完全相同也不例外。 - ## 模型体验 无。该包只是浏览器与其他插件所注册 HTTP/upgrade route 之间的 Web 载体,其中没有任何内容会进入模型请求。 diff --git a/packages/workspace/workspace/README.i18n.yaml b/packages/workspace/workspace/README.i18n.yaml index aa3db1bef6..7ba60b4109 100644 --- a/packages/workspace/workspace/README.i18n.yaml +++ b/packages/workspace/workspace/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/workspace/workspace/README.md -README.md: bfa044de50fc71dca95637487df50dc551d40e1b +README.md: 057765e38de9cc700210eb8edeb1ddc7ffc861ff README.zh.md: f3be9611eba45db3719ce76ad6625f4787fab9ac diff --git a/packages/workspace/workspace/README.md b/packages/workspace/workspace/README.md index bfa044de50..057765e38d 100644 --- a/packages/workspace/workspace/README.md +++ b/packages/workspace/workspace/README.md @@ -12,6 +12,7 @@ The entity/storage rationale lives in the [domain Agent Note](../../../.agents/n - `ctx.workspace.get(id)` / `list()` / `resolveByPath(path)` — cache-served lookups. `list()` is synchronous and follows durable registry order; `resolveByPath` is async because it applies the same `realpath` canon and rejects a missing path rather than creating it. - `ctx.workspace.delete(id)` — removes only the Workspace registration, its durable order entry, and its session account. Unknown ids return `false`; a removed record returns `true`. The directory, user files, live Sessions, and persisted session logs are never touched, so those Sessions become Ungrouped. A table-write failure restores the prior order and published entity. - `Workspace.attachSession(id)` — validates a live or persisted session header cwd against the workspace path and prepends a new id. Unknown sessions, absent/unresolvable/non-directory cwd values, and mismatches reject without writing. `detachSession` removes only the candidate index entry. +- `Workspace.insertSessionBefore(id, before?)` — moves an accounted session within the manual order, DOM-insertBefore-like: before the anchor, or appended when the anchor is omitted. A session or anchor absent from the account rejects without writing; a move to the current position resolves without writing. Workspace order never changes. - `ctx.workspace.archiveSession(id)` / `archivedSessionIds` — the registry-global archive set, layered over workspace accounting: an archived session disappears from grouping surfaces but keeps its session log and its `sessionIds` slot, so a future unarchive restores its position. Archiving accepts any live or persisted session (accounted or Ungrouped), resolves without writing for an already archived id, and rejects an unknown id. State written before the field existed parses with an empty set. - `Workspace.sessionIds` — synchronous id-plus-canonical-cwd membership projection in durable candidate order. Missing headers, invalid cwd values, and mismatches are filtered; the next workspace mutation prunes them. A medium indexing one session under two workspaces, claiming one path from two records, or diverging from durable workspace order rejects at startup. - `Workspace.status()` — uncached directory check, `'ok' | 'missing-dir'`; a missing directory never mutates the record. From e3af8d2ed247d5440c931973e3b33613568e5ed1 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:48:20 +0800 Subject: [PATCH 08/19] docs: add eight lean subsystem pages covering every remaining service permission, plan, invariants, http-server, storage (hub + backend seam + domain form + domain/changed), workspace, tui, and client-modules complete the docs/subsystems tier: every ctx service and event scope now has one owning page, the precondition for generating per-subsystem service/event reference into these pages. 25 new type-equiv manifest entries; 16 types move from TYPE_LINK_EXEMPTIONS to LINK_MAP now that they have catalog homes (dead InvariantRegistration exemption removed; catalogs regenerated); core.md's sub-page table gains the eight rows in both languages; the owning subsystems-catalog Agent Note records the coverage extension. Chinese counterparts and pair records follow in the next commit. --- ...2026-06-20-core-data-structures-catalog.md | 1 + ...6-06-20-core-data-structures-catalog.zh.md | 1 + docs/cordis-catalog/events.md | 2 + docs/subsystems/client-modules.md | 63 +++ docs/subsystems/core.md | 8 + docs/subsystems/core.zh.md | 8 + docs/subsystems/http-server.md | 49 ++ docs/subsystems/invariants.md | 59 ++ docs/subsystems/permission.md | 63 +++ docs/subsystems/plan.md | 39 ++ docs/subsystems/storage.md | 125 +++++ docs/subsystems/workspace.md | 121 ++++ .../tests/cordis-catalog-contract.spec.ts | 4 +- scripts/gen-cordis-catalog.ts | 521 +++++++++++++++--- scripts/type-equiv.manifest.json | 123 +++++ 15 files changed, 1104 insertions(+), 83 deletions(-) create mode 100644 docs/subsystems/client-modules.md create mode 100644 docs/subsystems/http-server.md create mode 100644 docs/subsystems/invariants.md create mode 100644 docs/subsystems/permission.md create mode 100644 docs/subsystems/plan.md create mode 100644 docs/subsystems/storage.md create mode 100644 docs/subsystems/workspace.md diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index 57433fdcb3..025cbd7ab7 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -58,3 +58,4 @@ The spine-vs-seam rule was tested against `BashExecRequest`, tool schemas and de - The spine-vs-seam line is a reusable scoping tool, not a one-off: the same "the thing you write/hold/receive is core; the machinery that types/renders/persists it is a detail" rule is what later scoped the events/services catalog's harness-vs-inherited tiering. - The `ts type-equiv` fence is a third doc-block category alongside ` ```ts ` (compiled) and ` ```ts ignore-check ` (sketch). A later sibling added a fourth, ` ```ts cordis-catalog ` (generated signature), reusing the same skip-and-exclude treatment. - Adding or reshaping a core type now carries a documentation obligation the author must honor (the gate cannot detect a missing *new* type), backstopped by the `dsh-code-review` checklist. +- Since 2026-07-27 the sub-page tier spans every service-bearing subsystem: the eight `ctx` services without a page (permission presets, plan mode, runtime invariants, the HTTP carrier, storage, TUI extensions, workspaces, client modules) gained lean pages, so each harness service and event scope has exactly one owning subsystems page — the precondition for generating per-subsystem service/event reference into these pages instead of flat catalogs. diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index 3956839c82..de644e8567 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -58,3 +58,4 @@ Status: implemented - 主干与 seam 分界线是一个可复用的范围界定工具,而非一次性的:同一条「你编写/持有/接收的东西是核心;为其提供类型推导/渲染/持久化的机制是细节」规则,后来也被用于界定事件/服务目录的 harness 层与继承层分层。 - `ts type-equiv` 围栏是继 ` ```ts `(编译)和 ` ```ts ignore-check `(草稿)之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `(生成签名),复用了相同的跳过并排除处理。 - 添加或重塑核心类型现在附带一项文档义务,作者必须履行(门禁无法检测缺失的*新*类型),由 `dsh-code-review` 检查清单兜底。 +- 自 2026-07-27 起,子页面层级覆盖每个承载服务的子系统:原先没有页面的八个 `ctx` 服务(权限预设、计划模式、运行时不变式、HTTP 载体、存储、终端扩展、工作区、客户端模块)都获得了精简页面,于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面(而非平铺目录)的前提。 diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index dd9cf5bfa5..6d060cf26b 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -389,6 +389,8 @@ A domain record or the global singleton changed, emitted once per write strictly 'domain/changed'(change: DomainChanged): void ``` +Types: [DomainChanged](../subsystems/storage.md) + Source: [`packages/storage/storage-domain/src/events.ts:46`](../../packages/storage/storage-domain/src/events.ts) ## `fs/*` diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md new file mode 100644 index 0000000000..bd5ecdc85e --- /dev/null +++ b/docs/subsystems/client-modules.md @@ -0,0 +1,63 @@ +# Client Modules + +English | [中文](client-modules.zh.md) + +The web plugin table: the Node half of the client module system in [dsh-client-modules](../../packages/client/modules), provided as `ctx.clientModuleHost` (`ClientModuleHostService`). It scans the host Loader's entries for `dshClient` packages, composes the `window.__DSH_BOOT__` entry graph, serves each bundle at `/plugins//client.js`, and taps the index render to inject the boot manifest — the four faces of one service. It is an optional capability of the web GUI stack, not part of the agent-loop spine, and it is a consumer of [dsh-host-webserver](../../packages/host/webserver): the carrier described in [http-server.md](http-server.md) supplies the prefix route and index tap this service registers. The same package's browser half (`ctx.modules`, the lazy-CJS module table that fetches and materializes these bundles) is kernel machinery documented in the [package README](../../packages/client/modules/README.md), not here. + +Source: [`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts) + +## The wire + +The graph is the wire single source between the Node and browser halves: the host composes `WebBootEntry` rows from scanned packages, injects the graph as the first script in `` (`window.__DSH_BOOT__`, with `<` escaped so plugin-controlled strings cannot break out of the script element), and the shell parses it before booting anything. A page without a valid manifest cannot boot — the browser-side parser throws loud on a missing or malformed graph. + +```ts type-equiv +/** + * One composed client entry pushed by the host (web2 §0 graph row). Wire + * single source: the host node half (package root) produces this same shape. + * `immediately` marks stage-one prefetch; `inject` is informational graph + * metadata (the authoritative edges live in each package's dshClient + * declaration and reach fibers through entry creation). + */ +interface WebBootEntry { + /** Entry name == package name. */ + id: string + /** Bundle endpoint, '/plugins//client.js?rev='. */ + url: string + /** Bundle content hash (cache-busting consistency anchor). */ + rev: string + /** Package-name dependency edges, informational (preflight display / HMR diffing). */ + inject?: string[] + /** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */ + immediately?: boolean +} +``` + +```ts type-equiv +/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */ +interface WebBootGraph { + /** Consistency anchor over the whole graph (content + bundle hashes). */ + rev: string + /** Composed entries; order carries no semantics (activation order is fiber inject waiting). */ + entries: WebBootEntry[] +} +``` + +Each row's `rev` is the bundle's content hash and rides the URL as a cache-busting query; the graph `rev` hashes the composed rows, so any row change changes it. `immediately` marks the stage-one prefetch tier (fetch and execute during module-face boot, registration only); a lazy row is fetched on first import. + +## The scan + +A package joins the table by declaring `dshClient` (`platform: 'web'`, optional `inject` edges, optional `immediately`) in its package.json and exporting its built bundle at `exports["./client"]`. Package resolution anchors at the config tree's `ctx.baseUrl` — the cordis.yml directory, whose package declares every composed plugin as a dependency — and construction throws when that anchor is unset. + +Scanning is incremental per package; there is no full-rescan code path. Every cordis `internal/plugin` emission (fiber construction or disposal) marks the fiber's entry name dirty, and a microtask flush reconciles each dirty name against the live loader entries. The activation pass seeds the same dirty set with all current entries and flushes synchronously, so first scan and steady state share one implementation — with opposite failure postures. At activation, a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud `AggregateError` listing every broken package: the fiber FAILS and the boot's fail-loud sweep reports it. In steady state, a broken package logs a warning and must not poison the others. + +Package metadata — including the negative "not a client package" verdict — is cached per name and never expires: plugin-set changes take effect on restart. A fiber restart reuses its row and rev untouched; bundle content changes reach the graph only through `rebuilt()`. + +## The bundle route and index tap + +`GET`/`HEAD /plugins//client.js` serves the registered bundle from disk with `no-cache` (the rev query, not HTTP caching, anchors consistency); other methods are 405. An unknown id — or a registered row whose bundle is unreadable because it has not been built yet — answers a loud 404 rather than letting the carrier's SPA fallback ship HTML as JavaScript. The index tap injects the current graph on every index render, so a reload always boots against the live composition. + +## The service + +`ClientModuleHostService` (`ctx.clientModuleHost`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxclientmodulehost--clientmodulehostservice). `graph()` returns the current composed graph (a stable object between changes) and `clientPath(id)` the bundle's absolute path. `rebuilt(id)` is the only entry point through which bundle content reaches the graph: it re-hashes the file, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush. + +In development, [dsh-client-hmr](../../packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from a synchronously captured baseline, calls `rebuilt(id)` on change, resyncs its watch set through `onGraphChanged`, and broadcasts rev changes to the browser half over SSE. Production graphs omit the HMR row entirely; the module host itself never watches files. diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 8f413a7a06..bde64a39e4 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -46,6 +46,14 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` | | [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` | | [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality | +| [permission.md](permission.md) | the permission-preset layer: `PresetSpec`/`PresetOption`, the derived `custom` state, the log-only `permission/preset` event | +| [plan.md](plan.md) | plan mode: the log-only `plan/mode` state, pending-selection flush, `PlanModeConfig`, the `exit_plan_mode` review arc | +| [invariants.md](invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract | +| [http-server.md](http-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the static dist fallback, index taps | +| [storage.md](storage.md) | the storage subsystem: the backend seam (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | +| [tui.md](tui.md) | the terminal-extension seam: `TuiOverlayRequest`/`Host`/`Session`, close reasons and outcomes, the modal queue | +| [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship | +| [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap | > Type declarations and their JSDoc on these pages are source-equivalent and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Ordinary blocks preserve complete declarations; `public-api` blocks preserve body-stripped public class declarations. Cordis services use the generated [service catalog](../cordis-catalog/services.md). diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index d0f02f0cfc..1f21785cad 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -46,6 +46,14 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数 | [web.md](web.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` | | [spill.md](spill.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` | | [workflow.md](workflow.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | +| [permission.md](permission.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 | +| [plan.md](plan.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 | +| [invariants.md](invariants.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空伴随插件契约 | +| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、静态 dist 回退、index 转换 | +| [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | +| [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 | +| [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | +| [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | > 这些页面上的类型声明及其 JSDoc 与源码等价,并由 `pnpm run verify-type-equiv` 检查漂移(见 [development.md](../development.md#documenting-types-verbatim-ts-type-equiv))。普通块保留完整声明;`public-api` 块保留去除实现体的公开 class 声明。Cordis 服务使用生成的[服务目录](../cordis-catalog/services.md)。 diff --git a/docs/subsystems/http-server.md b/docs/subsystems/http-server.md new file mode 100644 index 0000000000..565df72f27 --- /dev/null +++ b/docs/subsystems/http-server.md @@ -0,0 +1,49 @@ +# HTTP Server + +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. + +Source: [`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) + +## Routes + +```ts type-equiv +/** Route match kind: 'exact' matches the pathname verbatim; 'prefix' p matches p and p/. */ +type WebRouteKind = 'exact' | 'prefix' +``` + +```ts type-equiv +/** One named route registration. */ +interface WebRoute { + kind: WebRouteKind + /** Absolute pathname, no trailing slash. */ + path: string + /** Owns the full response lifecycle (may hold the response open, e.g. SSE). */ + handler: (req: IncomingMessage, res: ServerResponse) => void | Promise +} +``` + +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)). + +## Config + +```ts type-equiv +/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */ +interface Config { + /** Listen host; the two supported values are loopback and all-interfaces. */ + host: '127.0.0.1' | '0.0.0.0' + /** Listen port; zero requests an OS-assigned port. */ + port: number + /** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */ + distIndex: string +} +``` + +`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. `distIndex` is an assembly fact the composing app resolves and injects. + +## The service + +`HttpServerService` (`ctx.httpServer`) listens immediately on activation; a listen failure (EADDRINUSE…) throws out of init — a FAILED fiber the boot's fail-loud sweep reports. `register(route)` adds one named route and returns its disposer; a duplicate `(kind, path)` throws, because route patterns are a composition-level contract and a collision is a misconfiguration. `tapIndex(transform)` adds a pure html-to-html transform applied to every index response — `/` and each SPA fallback — in registration order; [dsh-client-modules](../../packages/client/modules) uses it to inject the boot manifest. `port` reads the listening port, the OS-assigned value when `config.port` is 0. + +A request whose handling throws (a malformed %-escape hitting `decodeURIComponent`, a client dropping mid-body) is logged as a warning and answered 400 — or the socket destroyed when headers are already out — never a process exit. Disposal pairs `close()` with `closeAllConnections()` because a handler may hold its response open (SSE) and such connections never end on their own; without the force-close, teardown would hang. The package never prints: the URL line belongs to the shell. Per-package operational detail, including the dev-mode bundle watch pipeline, stays in the [README](../../packages/host/webserver/README.md). diff --git a/docs/subsystems/invariants.md b/docs/subsystems/invariants.md new file mode 100644 index 0000000000..143a7feabb --- /dev/null +++ b/docs/subsystems/invariants.md @@ -0,0 +1,59 @@ +# Runtime Invariants + +English | [中文](invariants.zh.md) + +[dsh-invariants](../../packages/support/invariants) is the configurable registry service (`ctx.invariants`) for package-owned runtime invariant checks. It is one support-group package, not a three-package capability seam, and not part of the agent-loop spine: the registry owns selection, name reservation, child-fiber lifecycle, and package-attributed failure, while every workspace package publishes a `./invariant` companion plugin that registers checks under its exact npm package name. What a check may assert — authoritative event streams or mutable data, never service or method presence — is the runtime-invariants convention in [AGENTS.md](../../AGENTS.md#conventions); the seam design is owned by the [invariant-service Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md). + +Source: [`packages/support/invariants/src/index.ts`](../../packages/support/invariants/src/index.ts) + +## Selection + +```ts type-equiv +/** Runtime invariant selection configured on the service plugin. */ +interface Config { + /** Global switch; defaults to `true`. */ + readonly enabled?: boolean + /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ + readonly package_allowlist?: string[] + /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ + readonly package_blocklist?: string[] +} +``` + +A package is selected when the service is enabled, the allowlist is empty or at least one pattern matches its full npm name, and no blocklist pattern matches — a blocklist match overrides an allowlist match. Entries compile with `new RegExp(source)`: matching is unanchored unless the source supplies `^` and `$`, and `/pattern/flags` syntax is not parsed. Validation fails loud at service startup: a blank, whitespace-padded, duplicate, or invalid entry throws instead of being skipped. A valid pattern may match no currently loaded package, so later loading and HMR stay deterministic; filters are fixed for the service lifetime ([README](../../packages/support/invariants/README.md)). + +## The installer + +```ts type-equiv +/** + * Throw a package-attributed invariant failure. + * @param message - violated package contract without the standard prefix. + * @returns never because reporting a violation throws. + */ +type InvariantFailure = (message: string) => never +``` + +```ts type-equiv +/** Install one package's checks into the registration's child context. */ +interface InvariantInstaller { + /** + * Install the package contribution. + * @param ctx - child context owned by this invariant registration. + * @param fail - reporter bound to the registering package name. + * @returns nothing, or a promise settling after asynchronous checks finish. + */ + (ctx: Context, fail: InvariantFailure): void | Promise + /** Services the child installer fiber may access. */ + readonly inject?: Inject +} +``` + +An enabled installer runs in a dedicated child Cordis fiber; `installer.inject` declares the services that fiber may access, and synchronous or asynchronous installer completion is joined before the registration succeeds. `fail(message)` throws `InvariantError` — `extends Error` with stable `code: 'INVARIANT'`, the owning `packageName`, and a message prefixed `invariant violated by "": …` — so a violation is attributable without the registry importing any product package. + +## The service + +`ctx.invariants.register(packageName, installer)` reserves one active registration for the full npm package name and returns its effect-scoped disposer. The reservation holds even when filters keep the installer inactive, so two plugins can never silently claim the same package name; a duplicate, blank, or whitespace-containing name throws. An installer failure disposes the child fiber and releases the reservation atomically. The service owns every registration fiber while the returned disposer also belongs to the companion fiber: unloading either side removes listeners, trace state, and the reservation, so a companion can reload and register the same name again without retained state. + +## The companion contract + +Every workspace package owns a `./invariant` companion ([package contract](../../packages/AGENTS.md)); publication and registration are exhaustive, but assertions are deliberately not synthetic. A companion installs a check only when its package owns an observable event or mutable-data relationship; otherwise it exports an empty installer whose leading comment starts `No runtime invariant:` and explains, package-specifically, why nothing is checkable. `pnpm run verify-package-invariants` mechanically rejects generated markers, unexplained empty installers, non-empty installers that omit or ignore the reporter, incorrect registration names, and incomplete export, publication, dependency, or bundle wiring ([mechanical-rule Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md)). The catalog of executable companions and the standard composition live in the [package README](../../packages/support/invariants/README.md). diff --git a/docs/subsystems/permission.md b/docs/subsystems/permission.md new file mode 100644 index 0000000000..49dd51ff86 --- /dev/null +++ b/docs/subsystems/permission.md @@ -0,0 +1,63 @@ +# Permission Presets + +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. + +Source: [`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts) + +## The preset table + +A preset is a table key mapping to one sandbox/approval bundle plus optional client presentation; the default table ships `workspace-write` (`workspace-write` + `ask`) and `danger-full-access` (`danger-full-access` + `never`). + +```ts type-equiv +/** One preset's sandbox/approval bundle and optional client presentation. */ +interface PresetSpec { + /** The `sandbox/mode` value the preset writes through. */ + sandbox: SandboxMode + /** The `approval/policy` value the preset writes through. */ + approval: ApprovalPolicy + /** The display label a client shows for this preset; the raw table key when omitted. */ + name?: string + /** One user-facing sentence on what the preset means; omitted when not configured. */ + description?: string +} +``` + +```ts type-equiv +/** The {@link PermissionService} config: the deployment's preset table. */ +interface Config { + /** + * The preset table: name → knob bundle. Defaults to `workspace-write` + * (workspace-write + ask) and `danger-full-access` (danger-full-access + + * never). The name `custom` is reserved for the derived not-a-preset state. + */ + presets?: Record +} +``` + +The service requires a confining `ctx.bash` executor and `ctx.approval`, and misconfiguration fails at plugin load: a table entry named `custom` throws (the name is reserved for the derived not-a-preset state), and composing over a bash executor that does not confine (no `sandboxMode` capability fact) throws, because presets bundle a sandbox mode. + +## Current preset and the derived `custom` + +`current(events)` derives the effective preset from the knobs, not from its own event alone: it folds the session's effective sandbox mode (falling back to the executor's configured mode) and effective approval policy (falling back to the approval service config, then `ask`), prefers a still-matching recorded selection, then the first matching table entry in declaration order, and otherwise returns `CUSTOM_PRESET` (`'custom'`). `custom` is derived-only: clients may display it as the current value, but it is never a switch target or an event payload. + +`names` lists the switchable presets in table declaration order; `optionOf(name)` builds the option a client renders for a table key (label falls back to the key) or for `custom`, and throws for any other name. + +```ts type-equiv +/** The select-option shape a presentation layer advertises for one preset (or for the derived `custom` state). */ +interface PresetOption { + /** Stable option value: the table key, or `custom`. */ + value: string + /** The display label. */ + name: string + /** One user-facing sentence on what the value means. */ + description?: string +} +``` + +## Switching and the `permission/preset` event + +`set(session, name)` resolves the preset (unknown names throw), appends a log-only `permission/preset` event unless `name` is already the effective preset, then writes each knob through its own setter — `setSandboxMode` from [dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) and `setApprovalPolicy` from [dsh-user-approval](../../packages/ui/user-approval) — only when that knob's effective value changes. The selection event precedes the knob events in the same turn, and re-selecting the effective preset appends nothing at all. + +`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). diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md new file mode 100644 index 0000000000..f67e7734ca --- /dev/null +++ b/docs/subsystems/plan.md @@ -0,0 +1,39 @@ +# Plan Mode + +English | [中文](plan.zh.md) + +Plan mode is logged per-agent collaboration state owned by [dsh-plan-mode](../../packages/plan/plan-mode) (`ctx.planMode`, `PlanModeService`): while active, a deployment-owned guidance section shapes each model request. It is **soft guidance**, deliberately independent of the [sandbox mode](sandbox.md) and [approval policy](approval.md) enforcement axes — those knobs never read or write plan state, and deployments needing a hard boundary combine them separately. The package is one optional capability, not part of the agent-loop spine; its surfaces are the `plan:policy` prompt section, the always-registered `exit_plan_mode` tool, and the `/plan` command. The [design note](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md) owns the rationale; the [package README](../../packages/plan/plan-mode/README.md) owns the model-experience and limitation detail. + +Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/src/index.ts) + +## Logged state and recovery + +`plan/mode` (`{ active: boolean }`) is a log-only, whole-value-replace [session event](session.md): durable and replayable, never in the model transcript. `foldPlanMode(events, end?)` returns the last logged value in the prefix, or `false` when there is none — the state in force is always a pure fold of the session log, so resume, fork, and compaction recover it with no live mirror, and UIs observe committed flips through `session/event`. The complete event declaration is in the [persistence log event catalog](../persistence-catalog.md). + +## Pending intent and the turn-boundary flush + +Because every session event is turn-enclosed, a user selection is held as pending intent until a turn boundary. `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 service flushes one pending selection before the affected request assembly at three boundaries: prompt submission, ordinary turn continuation, and request-recovery retry. The flush runs after the downstream listener chain, so a selection arriving while an async listener awaits still shapes the request that boundary precedes. A flush failure is contained — plan policy can never block a prompt or 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)). + +## Configuration + +```ts type-equiv +/** Deployment-owned plan guidance. */ +interface PlanModeConfig { + /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */ + section: string +} +``` + +A missing, blank, or non-string `section` and any unknown key fail at plugin load rather than silently shaping nothing. While plan mode is active, the exact `section` text renders as the `plan:policy` [system-prompt section](system-prompt.md) at order 50; inactive plan mode contributes no text. + +## The exit tool and the `/plan` command + +[`exit_plan_mode`](../tool-catalog.md#deepseek-aidsh-plan-mode) stays registered while plan mode is inactive, so crossing the boundary changes only the prompt section, never the request tool catalog; execution outside plan mode fails. In plan mode it requires a complete markdown plan starting with a `#` heading and presents it for review through the [user-interaction seam](user-interaction.md). Approval returns `{ approved: true }` and records a silent (non-narrated) pending exit that flushes after the step — plan guidance holds for the rest of the assistant's tool batch, and the tool result itself narrates the transition. Keep-planning is a failed call carrying the user's feedback, so the model revises and presents again; a missing interaction channel and a service reload during review also fail the call rather than silently leaving plan mode. + +When [`ctx.commands`](commands.md) is composed, the plugin registers `/plan [off|message]`: bare `/plan` selects plan mode, any other non-empty message selects it and then submits the text through `agent.steer()` so it becomes the next step's ordinary logged user message under plan guidance, and the exact argument `off` selects inactive — which also cancels a not-yet-flushed pending entry before plan mode ever reaches a request. + +## The service + +`ctx.planMode` owns the logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool; `get`/`set` signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxplanmode--planmodeservice). diff --git a/docs/subsystems/storage.md b/docs/subsystems/storage.md new file mode 100644 index 0000000000..5d54d7337b --- /dev/null +++ b/docs/subsystems/storage.md @@ -0,0 +1,125 @@ +# Storage + +English | [中文](storage.zh.md) + +The storage subsystem persists everything that is not a session event log (session logs have their own seam — [persistence.md](persistence.md)). It is one optional capability, not part of the agent-loop spine, split as a [capability seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md): the hub and backend interface ([dsh-storage](../../packages/storage/storage), `ctx.storage`), the backend implementations ([dsh-storage-json](../../packages/storage/storage-json), registered as `json`, and [dsh-storage-sqlite](../../packages/storage/storage-sqlite), registered as `sqlite`), and the domain data form ([dsh-storage-domain](../../packages/storage/storage-domain), `ctx.storageDomain`, also reachable as `ctx.storage.domain`) — the backend seam's only consumer and the typed API everything else uses. The hub performs no IO itself: backends own media, data forms own semantics, and product packages never touch backends directly. Design record: [domain KV storage Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md). + +Source: [`packages/storage/storage/src/backend.ts`](../../packages/storage/storage/src/backend.ts) · [`packages/storage/storage-domain/src/spec.ts`](../../packages/storage/storage-domain/src/spec.ts) · [`packages/storage/storage-domain/src/events.ts`](../../packages/storage/storage-domain/src/events.ts) + +## The hub: `ctx.storage` + +`Storage` ([signatures](../cordis-catalog/services.md#ctxstorage--storage)) is a meeting point, not a store. `ctx.storage.backend` is a name → backend table: multiple backends stay mounted side by side, and which backend serves which consumer is that consumer's configuration (the domain layer's route table), never a hub-global choice. `register(name, backend)` returns the disposer; duplicate names and unknown lookups throw `StorageError`. Disposal only unregisters the name — the owning plugin closes the backend after unregistering. Each backend plugin also publishes a lifecycle-only service key (`storageBackendServiceKey(name)`), which form providers inject so their activation cannot race backend registration. + +Data forms mount on the hub under a merge-extensible key map: + +```ts type-equiv +/** + * Data forms mountable on the hub, keyed by form name. Form owners extend + * this map via declaration merging (the domain layer merges + * `domain: DomainFacility`) and mount the facility in their `apply`. + */ +interface StorageForms {} +``` + +`mount(form, facility)` is an effect whose disposer unmounts; a second mount of the same key throws `duplicate-mount`. `form(form)` resolves a mounted facility and throws `form-not-mounted` until the owning plugin loads — assemblies order plugins accordingly rather than silently deferring. The domain layer merges `domain: DomainFacility`, so `ctx.storage.domain` and `ctx.storageDomain` are the same object. + +## The backend seam + +```ts type-equiv +/** + * One registered backend. A backend owns exactly one medium and shares its + * lifecycle across all facets; facets are optional members — a backend that + * cannot serve a shape simply omits it, and resolution fails loud instead. + */ +interface StorageBackend { + /** Key-value data shape; absent when this backend cannot serve it. */ + readonly kv?: KvFacet + + /** + * Drain in-flight writes across all open units and release the medium. + * Idempotent; concurrent and repeated calls resolve once teardown finishes. + * @returns resolution after the medium is released. + */ + close(): Promise +} +``` + +A backend owns one medium (a file-tree root, a database file) and exposes optional data-shape facets; `kv` is the only facet today. `KvFacet.open(descriptor)` opens one named unit — `KvUnitDescriptor` carries the name, format version, table names, and whether a global singleton slot exists — and returns a `KvUnit` with `loadAll`, `putRecord`, `deleteRecord`, `setGlobal`, and `close`. Unit and table names must match `UNIT_NAME_RE` (safe as a file name and as a SQL identifier segment); record keys are arbitrary strings that never reach file paths. A unit does not serialize concurrent writes — ordering belongs to the caller — but each single call is atomic on the medium and durable once resolved. A medium stamped with a different version rejects `version-mismatch`; one that cannot be parsed as the unit rejects `malformed-medium` (no migration, pre-release stance). [`backend.ts`](../../packages/storage/storage/src/backend.ts) is the normative clause-by-clause contract, and the shared conformance suite in [`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) asserts every clause against each backend. The [json backend](../../packages/storage/storage-json/README.md) republishes one whole human-readable file per unit atomically; the [sqlite backend](../../packages/storage/storage-sqlite/README.md) stores document-per-row in one database, the route for high-churn domains. + +## Declaring a domain + +A domain is declared once by its owning package as a spec object — the single source of the domain's identity, layout, and record schemas (zod, so `z.infer` keeps consumer types un-duplicated): + +```ts type-equiv +/** Static declaration of one domain: identity, version, and record layout. */ +interface DomainSpec { + /** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */ + readonly name: string + /** Domain format version; a medium stamped with a different version rejects at open. */ + readonly version: number + /** Optional global singleton slot. */ + readonly global?: DomainGlobalSpec + /** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */ + readonly tables: Record +} +``` + +`defineDomain(spec)` pins the spec's literal types and fails loud at the owner's module load, before any medium is touched: a domain or table name outside `UNIT_NAME_RE`, a version that is not a non-negative integer, or a global schema that accepts `null` all throw (`null` is the medium's "never written" sentinel, so a stored nullable global could not round-trip). `domainTable(schema)` declares one table with a phantom compile-time key type (typically a [branded id](core.md#branded-ids)); `descriptorOf(spec)` projects the backend-facing unit descriptor. + +## The open domain + +```ts type-equiv +/** One open domain, typed by its spec. */ +interface Domain { + /** Domain name from the spec. */ + readonly name: string + /** Global singleton handle; a spec without `global` has no usable handle (`never`). */ + readonly global: DomainGlobalHandleOf + /** + * Resolve one declared table handle. Handles are stable — repeated calls + * return the same instance. + * @param name - Declared table name. + * @returns the typed table handle. + */ + table(name: N): KvTable, TableValueOf> + + /** + * Close this domain: reject new writes immediately, drain already-queued + * writes (their events still emit), release the backend unit, then free + * the domain name for a later open. Idempotent — repeated calls share one + * teardown. The consumer owns this call (typically as its own `ctx.effect` + * disposer); the facility closes any domain left open when it unmounts. + * @returns resolution after the unit is released. + */ + close(): Promise +} +``` + +Reads are synchronous from authoritative in-memory state: `KvTable` exposes `get`/`entries`/`keys`/`size` (snapshot iterators that stay stable while queued writes land), and the global handle's `get()` serves the spec's `initial` until the first `set` materializes the slot on the medium. Every write — `put`, `delete`, `update`, `global.set` — queues on one per-domain chain and reaches backend durability first, then mutates memory, then emits `domain/changed`; a rejected backend write leaves memory untouched, so reads never diverge from the medium. `update(key, fn)` is an atomic read-modify-write at its chain slot (a missing key rejects `missing-key`); `delete` of an absent key resolves `false` with no write and no event. Returned records are the stored objects themselves, not copies — replace via `put`/`update`, never mutate in place. + +## The domain facility: `ctx.storageDomain` + +`DomainFacility` ([signatures](../cordis-catalog/services.md#ctxstoragedomain--domainfacility)) opens declared domains over routed backends. Routing is the domain plugin's configuration, never the hub's: `backend` names the required default route and `routes` overrides it per domain name. `open(spec)` runs a strict sequence, each step failing the whole call: it rejects a name already open or still closing (`already-open`), resolves the route (`backend-not-found`), requires the backend's `kv` facet (`facet-unsupported`), opens the unit (backend `version-mismatch`/`malformed-medium` pass through), and validates every stored record and global against the spec's zod schemas (`invalid-record` with the offending table and key). The caller owns the returned handle and releases it with `Domain.close()`; domains still open when the plugin unmounts are closed by the facility, and a closed domain's name frees for reopening only after teardown fully completes. `get(name)` is an untyped diagnostic lookup onto the package-private `DomainImpl` runtime behind every typed handle; `closeAll()` is the unmount path. + +## The change event: `domain/changed` + +Every durable write emits one event strictly after the backend acknowledged durability, in the domain's write-chain order ([event entry](../cordis-catalog/events.md#domainchanged--emit)): + +```ts type-equiv +/** Shared location fields of one durable domain change. */ +interface DomainChangedBase { + /** Owning domain name. */ + readonly domain: string + /** Table name; `''` for a global-singleton write. */ + readonly table: string + /** Record key; `''` for a global-singleton write. */ + readonly key: string +} +``` + +```ts type-equiv +/** One durable domain change; a closed union — switch on `operation`. */ +type DomainChanged = DomainChangedPut | DomainChangedDeleted +``` + +`put` (inserts, overwrites, and global writes) carries the new snapshot in `value` — never the old value; a diffing consumer keeps its own previous snapshot. `deleted` is a tombstone with no value. The event is a notification, not a transaction participant: the commit point has passed at emission, so a synchronously throwing listener is contained with a logged warning rather than rejecting the already-durable write, and emitted values equal the in-memory state at emission. The event is in-process only; cross-process change push is deferred work recorded in the [package README](../../packages/storage/storage-domain/README.md). diff --git a/docs/subsystems/workspace.md b/docs/subsystems/workspace.md new file mode 100644 index 0000000000..2726115457 --- /dev/null +++ b/docs/subsystems/workspace.md @@ -0,0 +1,121 @@ +# Workspaces + +English | [中文](workspace.zh.md) + +A workspace is the persistent record of a directory the user works in: a stable id over a canonical path, a display title, and the ordered account of sessions that belong to it. The subsystem is one package ([dsh-workspace](../../packages/workspace/workspace), `ctx.workspace`) — an optional host-side capability, not part of the agent-loop spine, and invisible to models (no tools, no prompt text, no session events). It stores its records through the [storage domain form](storage.md) and validates session membership against [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log), so `storageDomain` and `sessionPersistence` are mandatory startup dependencies: an unavailable persistence peer leaves the plugin pending rather than being mistaken for an empty history. Design record: [domain KV storage Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md); bootstrap and GUI ordering: [Workspace UI product-flow Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md). + +Source: [`packages/workspace/workspace/src/types.ts`](../../packages/workspace/workspace/src/types.ts) + +## Identity + +```ts type-equiv +/** + * Identifies one workspace record. A generated uuid, never the path: path + * normalization rewrites paths, and a reference anchor must stay stable. + */ +type WorkspaceId = Branded<'WorkspaceId'> +``` + +`WorkspaceId` is a [branded id](core.md#branded-ids). Path identity is separate: `realpathNormalize` (`fs.realpath`; trailing slashes, `..`, and symlinks resolved) is the one uniqueness canon — workspace paths are stored canonicalized, uniqueness is string equality of canonical paths (a symlink to an owned directory collides), and attach-time session cwd checks go through the same canon. + +## The workspace entity + +Consumers see only the `Workspace` interface; the implementation stays package-private. + +```ts type-equiv +/** + * One workspace: a stable id over an existing directory, a display title, and + * an ordered candidate account of sessions. Membership requires both an id in + * that account and a session header whose canonical cwd equals the workspace + * path. Consumers only see this interface; the implementation stays private. + */ +interface Workspace { + /** Stable record id (generated uuid). */ + readonly id: WorkspaceId + + /** + * Canonical directory path: the `fs.realpath` of the path given at create + * time (trailing slashes, `..`, and symlinks all resolved). Never rewritten + * afterwards, even when the directory disappears (see {@link status}). + */ + readonly path: string + + /** Display title. Defaults to `basename(path)` at create; duplicates are allowed. */ + readonly title: string + + /** ISO-8601 creation instant, stamped at create and never rewritten. */ + readonly createdAt: string + + /** ISO-8601 instant of the last durable mutation (create counts as one). */ + readonly updatedAt: string + + /** + * Header-validated sessions in manually owned order: a new session is + * prepended at attach, explicit reordering goes through + * `insertSessionBefore`, and activity never reorders. The durable candidate + * account is filtered synchronously: missing headers, invalid cwd values, + * and canonical cwd mismatches are never returned. A subsequent workspace + * mutation prunes those filtered candidates durably. + */ + readonly sessionIds: readonly SessionId[] + + /** + * Replace the display title durably. + * @param title - New title; any string, duplicates across workspaces allowed. + * @returns resolution after durability. + */ + setTitle(title: string): Promise + + /** + * Prepend a session to this workspace's candidate account. An already + * accounted id resolves without writing. A new id's live or persisted + * header cwd must resolve to an existing directory equal to {@link path}; + * unknown ids, missing or invalid cwd values, and mismatches reject without + * writing. + * @param sessionId - The session to record. + * @returns resolution after durability. + */ + attachSession(sessionId: SessionId): Promise + + /** + * Move an accounted session within the manual order, DOM-insertBefore-like: + * with an anchor the session lands before it, without one it appends to the + * end. Only the moved id changes position. A session or anchor absent from + * the account rejects without writing; a move to the current position + * resolves without writing (decided on the domain write chain). + * @param sessionId - The accounted session to move. + * @param beforeSessionId - Accounted anchor to insert before; omitted appends. + * @returns resolution after durability. + */ + insertSessionBefore(sessionId: SessionId, beforeSessionId?: SessionId): Promise + + /** + * Remove a session from this workspace's account. Idempotent: an id not on + * the account resolves without writing (decided on the domain write chain, + * like attach). Never touches the session's own stored log. + * @param sessionId - The session to remove. + * @returns resolution after durability. + */ + detachSession(sessionId: SessionId): Promise + + /** + * Live directory check, uncached: whether {@link path} currently exists and + * is a directory. A missing directory never mutates the record — the + * directory may only be temporarily moved. + * @returns `'ok'` when the directory exists, `'missing-dir'` otherwise. + */ + status(): Promise<'ok' | 'missing-dir'> +} +``` + +Ownership truth is the record's ordered `sessionIds`, never derived from session cwd — but membership requires both: an id on the account and a header whose canonical cwd equals the workspace path, so one session structurally belongs to at most one workspace. Failed writes reject (`insertSessionBefore` account errors as `WorkspaceMoveInvalidError`, storage failures as plain errors); every accepted mutation stamps `updatedAt` and durably prunes candidates that no longer pass the membership check. + +## The registry: `ctx.workspace` + +`WorkspaceRegistry` ([signatures](../cordis-catalog/services.md#ctxworkspace--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup completes exactly the marked mutation, and an unmarked order/table mismatch fails loud as corruption. + +Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`. + +## Consumers + +[dsh-host-apiproxy](../../packages/host/apiproxy) is the product consumer: it serves workspace CRUD to GUI clients over `ctx.workspace` and performs the create-session-then-attach flow above. [dsh-workspace-context](../../packages/context/workspace-context) is **not** a consumer despite the name: it discovers AGENTS.md-style instruction files under an agent's own cwd and never touches `ctx.workspace` — the shared word refers to the user's working directory, not to this registry's entities. diff --git a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts index edf653c3f4..30ef60e8fe 100644 --- a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts @@ -152,11 +152,11 @@ describe.skip('gen-cordis-catalog collectEvents', { timeout: 60_000 }, () => { it('accepts linked, foundation, generic-parameter, and explicitly exempt signature types', () => { const events = collectEvents(make( - ' /**\n * Carry linked and foundation types.\n * @param value - the linked value.\n * @param preset - deployment metadata outside the core catalog.\n * @param signal - cancellation.\n * @mode parallel\n */\n \'fix/typed\'(value: Readonly, preset: PresetSpec, signal: AbortSignal): Promise', + ' /**\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\'(value: Readonly, assembly: PromptAssembly, signal: AbortSignal): Promise', )) expect(events).toHaveLength(1) expect(renderEvents(events)).toContain('Types: [SessionEvent](../subsystems/core.md)') - expect(renderEvents(events)).not.toContain('[PresetSpec]') + expect(renderEvents(events)).not.toContain('[PromptAssembly]') }) it('aggregates every unclassified signature type with its source and remediation', () => { diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index d9f3803547..b710bebe76 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -1,25 +1,32 @@ /** - * Generate committed Cordis artifacts from the Typert catalog projector and - * the independent vendored-core projector. + * Generate the Cordis event and service catalogs from static declarations. + * The walk enforces event modes, JSDoc parameter/return completeness, and + * signature type-link coverage; inherited Cordis services come from the + * curated table below. `--check` verifies both committed artifacts. */ -import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' -import { dirname, resolve } from 'node:path' -import { - projectCordisCatalog, - renderEvents, - renderServices, -} from '@deepseek-ai/dsh-typert-generator' -import type { CordisCatalogPolicy } from '@deepseek-ai/dsh-typert-generator' +import { globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve, sep } from 'node:path' +import ts from 'typescript' 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 OUT_EVENTS = 'docs/cordis-catalog/events.md' const OUT_SERVICES = 'docs/cordis-catalog/services.md' -const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts' -/** One primary subsystems page per project type used by a generated signature. */ -export const LINK_MAP: Readonly> = { +/** The fenced-block info string for generated signature blocks (skipped by + * doc-typecheck, since a bare signature fragment is not standalone-compilable). */ +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 = { Agent: 'core.md', AgentCancelCause: 'core.md', AgentOptions: 'core.md', @@ -28,6 +35,7 @@ export const LINK_MAP: Readonly> = { ContinuationDecision: 'core.md', ContinuationStop: 'core.md', GenerateOptions: 'core.md', + InboxPlacement: 'core.md', MessageId: 'core.md', HookContext: 'core.md', SettleReason: 'core.md', @@ -49,7 +57,9 @@ export const LINK_MAP: Readonly> = { UserMessage: 'session.md', PreStepDecision: 'core.md', PreStepContext: 'core.md', + PromptDecision: 'core.md', RequestErrorAction: 'core.md', + RequestError: 'core.md', RequestFailureContext: 'core.md', PreparedReferencedMessage: 'session-reference.md', SessionReferenceCandidate: 'session-reference.md', @@ -106,7 +116,6 @@ export const LINK_MAP: Readonly> = { PreparedLlmCall: 'llm-streaming.md', LlmService: 'llm-streaming.md', StreamChunk: 'llm-streaming.md', - SkillProviderControl: 'skills.md', CreateSessionOptions: 'persistence.md', PrepareSessionOptions: 'persistence.md', SessionHeader: 'persistence.md', @@ -159,11 +168,9 @@ export const LINK_MAP: Readonly> = { SessionTitleObservationResult: 'session-query.md', SessionTitleProvider: 'session-title.md', SessionTitleSnapshot: 'session-title.md', - SkillCatalogSnapshot: 'skills.md', SkillDefinition: 'skills.md', SkillLookupOptions: 'skills.md', SkillProvider: 'skills.md', - SkillProviderObservation: 'skills.md', SkillRegistration: 'skills.md', SkillSummary: 'skills.md', SaveTextSpill: 'spill.md', @@ -232,15 +239,27 @@ export const LINK_MAP: Readonly> = { WorkflowRun: 'workflow.md', WorkflowRunInfo: 'workflow.md', WorkflowStartRequest: 'workflow.md', + PresetOption: 'permission.md', + PresetSpec: 'permission.md', + InvariantInstaller: 'invariants.md', + WebRoute: 'http-server.md', + StorageBackend: 'storage.md', + StorageForms: 'storage.md', + Domain: 'storage.md', + DomainSpec: 'storage.md', + DomainChanged: 'storage.md', + DomainFacility: 'storage.md', + Workspace: 'workspace.md', + WorkspaceId: 'workspace.md', + WebBootGraph: 'client-modules.md', } -/** TypeScript lib and pinned framework types with no repository-owned data page. */ -export const FOUNDATION_TYPE_NAMES: ReadonlySet = new Set([ +/** TypeScript lib and pinned framework types that have no repository-owned data page. */ +const FOUNDATION_TYPE_NAMES = new Set([ 'AbortSignal', 'AsyncIterable', 'Context', 'Error', - 'Map', 'Partial', 'Pick', 'Promise', @@ -248,8 +267,8 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet = new Set([ 'Readonly', ]) -/** Project types deliberately documented outside the core-data catalog. */ -export const TYPE_LINK_EXEMPTIONS: Readonly> = { +/** Project types deliberately documented outside the subsystems catalog. */ +const TYPE_LINK_EXEMPTIONS: Readonly> = { AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md', 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', @@ -261,15 +280,8 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { 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', 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', - Domain: 'domain interface is owned by packages/storage/storage-domain/README.md', - DomainChanged: 'event-local snapshot is owned by packages/storage/storage-domain/src/events.ts', - DomainFacility: 'domain form facility is owned by packages/storage/storage-domain/README.md', DomainImpl: 'domain implementation contract is owned by packages/storage/storage-domain/README.md', - DomainSpec: 'domain declaration contract is owned by packages/storage/storage-domain/README.md', - StorageBackend: 'backend contract is owned by packages/storage/storage/src/backend.ts', - StorageForms: 'merge-extensible form map is owned by packages/storage/storage/src/index.ts', ProjectionDefinition: 'projection unit contract is owned by packages/session/session-projection/README.md', SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session/session-projection/src/types.ts', ProjectionChangeListener: 'change-feed listener contract is owned by packages/session/session-projection/src/index.ts', @@ -285,18 +297,13 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { TypeRTDisposer: 'TypeRT lifecycle contract is owned by packages/typert/type-meta/README.md', 'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API', 'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API', - InvariantInstaller: 'service-local contribution contract is owned by packages/support/invariants/README.md', LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts', - WebBootGraph: 'web boot graph wire shape is owned by packages/client/modules/src/client/index.ts', - WebRoute: 'route registration contract is owned by packages/host/webserver/src/index.ts', - WebUpgradeRoute: - 'upgrade route registration contract is owned by packages/host/webserver/src/index.ts', ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts', Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts', + WebUpgradeRoute: + 'upgrade route registration contract is owned by packages/host/webserver/src/index.ts', InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md', InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md', - PresetOption: 'deployment menu metadata is owned by packages/interaction/permission/README.md', - PresetSpec: 'deployment preset composition is owned by packages/interaction/permission/README.md', KnobState: 'projection unit state shape is owned by packages/interaction/permission/README.md', PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts', PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', @@ -309,56 +316,403 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts', WorkflowAgentInfo: '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', - Workspace: 'workspace entity contract is owned by packages/workspace/workspace/README.md', - WorkspaceId: 'branded id is owned by packages/workspace/workspace/README.md', } -/** Repository data policy consumed by the Cordis catalog projector. */ -export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = { - linkedTypePages: LINK_MAP, - foundationTypeNames: FOUNDATION_TYPE_NAMES, - typeLinkExemptions: TYPE_LINK_EXEMPTIONS, - inheritedEvents: [ - { 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:22' }, - { name: 'hmr/config-update-failed', summary: 'A watched config-file refresh failed.', source: 'vendor/hmr/src/index.ts:29' }, - { 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' }, - ], - 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' }, - ], +/** Collect named references from parameter, generic-constraint/default, and return types. */ +function signatureTypeNames(member: ts.MethodSignature | ts.MethodDeclaration, sf: ts.SourceFile): string[] { + const declared = new Set(member.typeParameters?.map(parameter => parameter.name.text) ?? []) + const referenced = new Set() + const visit = (node: ts.Node): void => { + if (ts.isTypeReferenceNode(node)) referenced.add(node.typeName.getText(sf)) + if (ts.isTypeQueryNode(node)) referenced.add(node.exprName.getText(sf)) + ts.forEachChild(node, visit) + } + for (const parameter of member.typeParameters ?? []) { + if (parameter.constraint) visit(parameter.constraint) + if (parameter.default) visit(parameter.default) + } + for (const parameter of member.parameters) { + if (parameter.type) visit(parameter.type) + } + if (member.type) visit(member.type) + return [...referenced].filter(name => !declared.has(name)).sort() } -/** CLI entry: default writes every artifact; `--check` reports stale files. - * @returns nothing; writes files or reports freshness through the process. +/** Append fail-closed signature type-link violations with actionable ownership choices. */ +function checkTypeLinks( + 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.` 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. */ -export function main(): void { - const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY) +function jsDocText(text: string, sf: ts.SourceFile, node: ts.Node): string { + const raw = rawJsDoc(text, node) + 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.` 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() + 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 = [ + '', + '', +] + +/** 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.` 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.` 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][] = [ - [OUT_EVENTS, renderEvents([...model.events], CORDIS_CATALOG_POLICY)], - [OUT_SERVICES, renderServices([...model.services], CORDIS_CATALOG_POLICY)], - [OUT_RUNTIME_API, projector.renderRuntimeApi(model)], + [OUT_EVENTS, renderEvents(collectEvents())], + [OUT_SERVICES, renderServices(collectServices())], ...renderCordisCoreApiPages(), ] if (process.argv.includes('--check')) { @@ -368,7 +722,9 @@ export function main(): void { try { committed = readFileSync(resolve(root, out), 'utf8') } catch { - // Only ENOENT is expected; either read failure has the same remedy. + // Only ENOENT (not yet generated) is expected; a present-but-unreadable + // 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 } if (committed !== content) stale.push(out) @@ -389,4 +745,7 @@ export function main(): void { console.log(`gen-cordis-catalog: wrote ${outputs.length} generated file(s).`) } -if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) main() +// 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() +} diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 56c1b094be..b53ff60989 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -1564,6 +1564,129 @@ "doc": "docs/subsystems/typert.md", "symbol": "TypeRTClientRemote", "source": "packages/typert/type-meta/src/types.ts" + "doc": "docs/subsystems/permission.md", + "symbol": "PresetSpec", + "source": "packages/interaction/permission/src/index.ts" + }, + { + "doc": "docs/subsystems/permission.md", + "symbol": "Config", + "source": "packages/interaction/permission/src/index.ts" + }, + { + "doc": "docs/subsystems/permission.md", + "symbol": "PresetOption", + "source": "packages/interaction/permission/src/index.ts" + }, + { + "doc": "docs/subsystems/plan.md", + "symbol": "PlanModeConfig", + "source": "packages/plan/plan-mode/src/index.ts" + }, + { + "doc": "docs/subsystems/invariants.md", + "symbol": "Config", + "source": "packages/support/invariants/src/index.ts" + }, + { + "doc": "docs/subsystems/invariants.md", + "symbol": "InvariantFailure", + "source": "packages/support/invariants/src/index.ts" + }, + { + "doc": "docs/subsystems/invariants.md", + "symbol": "InvariantInstaller", + "source": "packages/support/invariants/src/index.ts" + }, + { + "doc": "docs/subsystems/http-server.md", + "symbol": "WebRouteKind", + "source": "packages/host/webserver/src/index.ts" + }, + { + "doc": "docs/subsystems/http-server.md", + "symbol": "WebRoute", + "source": "packages/host/webserver/src/index.ts" + }, + { + "doc": "docs/subsystems/http-server.md", + "symbol": "Config", + "source": "packages/host/webserver/src/index.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "StorageForms", + "source": "packages/storage/storage/src/index.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "StorageBackend", + "source": "packages/storage/storage/src/backend.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "DomainSpec", + "source": "packages/storage/storage-domain/src/spec.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "Domain", + "source": "packages/storage/storage-domain/src/domain.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "DomainChangedBase", + "source": "packages/storage/storage-domain/src/events.ts" + }, + { + "doc": "docs/subsystems/storage.md", + "symbol": "DomainChanged", + "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", + "symbol": "WorkspaceId", + "source": "packages/workspace/workspace/src/types.ts" + }, + { + "doc": "docs/subsystems/workspace.md", + "symbol": "Workspace", + "source": "packages/workspace/workspace/src/types.ts" + }, + { + "doc": "docs/subsystems/client-modules.md", + "symbol": "WebBootEntry", + "source": "packages/client/modules/src/client/manifest.ts" + }, + { + "doc": "docs/subsystems/client-modules.md", + "symbol": "WebBootGraph", + "source": "packages/client/modules/src/client/manifest.ts" } ] } From 3f9c58855b0c11e6edf0385d49f8cf1cf491edf3 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:29:15 +0800 Subject: [PATCH 09/19] docs: telemetry subsystem page and Chinese counterparts for the nine new pages telemetry.md joins the eight pages after the OTel telemetry seam landed on master (ctx.telemetry + telemetry/record were the one service and event scope still without a page). All nine pages gain zh counterparts translated whole-document per dsh-translate-docs; the eleven touched pairs (nine new + core + the subsystems-catalog note) are recorded; TelemetryRecord moves from TYPE_LINK_EXEMPTIONS to LINK_MAP and the catalogs are regenerated. --- ...-20-core-data-structures-catalog.i18n.yaml | 4 +- ...2026-06-20-core-data-structures-catalog.md | 2 +- ...6-06-20-core-data-structures-catalog.zh.md | 2 +- docs/subsystems/client-modules.i18n.yaml | 6 + docs/subsystems/client-modules.zh.md | 63 ++++++++ docs/subsystems/core.i18n.yaml | 4 +- docs/subsystems/core.md | 1 + docs/subsystems/core.zh.md | 1 + docs/subsystems/http-server.i18n.yaml | 6 + docs/subsystems/http-server.zh.md | 49 ++++++ docs/subsystems/invariants.i18n.yaml | 6 + docs/subsystems/invariants.zh.md | 59 +++++++ docs/subsystems/permission.i18n.yaml | 6 + docs/subsystems/permission.zh.md | 63 ++++++++ docs/subsystems/plan.i18n.yaml | 6 + docs/subsystems/plan.zh.md | 39 +++++ docs/subsystems/storage.i18n.yaml | 6 + docs/subsystems/storage.zh.md | 125 +++++++++++++++ docs/subsystems/telemetry.i18n.yaml | 6 + docs/subsystems/telemetry.md | 109 +++++++++++++ docs/subsystems/telemetry.zh.md | 109 +++++++++++++ docs/subsystems/workspace.i18n.yaml | 6 + docs/subsystems/workspace.zh.md | 121 ++++++++++++++ scripts/gen-cordis-catalog.ts | 2 +- scripts/type-equiv.manifest.json | 149 +++++++++--------- 25 files changed, 872 insertions(+), 78 deletions(-) create mode 100644 docs/subsystems/client-modules.i18n.yaml create mode 100644 docs/subsystems/client-modules.zh.md create mode 100644 docs/subsystems/http-server.i18n.yaml create mode 100644 docs/subsystems/http-server.zh.md create mode 100644 docs/subsystems/invariants.i18n.yaml create mode 100644 docs/subsystems/invariants.zh.md create mode 100644 docs/subsystems/permission.i18n.yaml create mode 100644 docs/subsystems/permission.zh.md create mode 100644 docs/subsystems/plan.i18n.yaml create mode 100644 docs/subsystems/plan.zh.md create mode 100644 docs/subsystems/storage.i18n.yaml create mode 100644 docs/subsystems/storage.zh.md create mode 100644 docs/subsystems/telemetry.i18n.yaml create mode 100644 docs/subsystems/telemetry.md create mode 100644 docs/subsystems/telemetry.zh.md create mode 100644 docs/subsystems/workspace.i18n.yaml create mode 100644 docs/subsystems/workspace.zh.md diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml index 13c54fa458..5bc51a4e7d 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md -2026-06-20-core-data-structures-catalog.md: 57433fdcb3c77976c4ba0cfe7d157f94dff331dd -2026-06-20-core-data-structures-catalog.zh.md: 3956839c8211e294280805d9b6993e5607bfe04f +2026-06-20-core-data-structures-catalog.md: 718045bf7ce511a0b766e69e64ef7657eb221189 +2026-06-20-core-data-structures-catalog.zh.md: 1e8473a373c212fa08b171719edd5803e5aa3b05 diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index 025cbd7ab7..718045bf7c 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -58,4 +58,4 @@ The spine-vs-seam rule was tested against `BashExecRequest`, tool schemas and de - The spine-vs-seam line is a reusable scoping tool, not a one-off: the same "the thing you write/hold/receive is core; the machinery that types/renders/persists it is a detail" rule is what later scoped the events/services catalog's harness-vs-inherited tiering. - The `ts type-equiv` fence is a third doc-block category alongside ` ```ts ` (compiled) and ` ```ts ignore-check ` (sketch). A later sibling added a fourth, ` ```ts cordis-catalog ` (generated signature), reusing the same skip-and-exclude treatment. - Adding or reshaping a core type now carries a documentation obligation the author must honor (the gate cannot detect a missing *new* type), backstopped by the `dsh-code-review` checklist. -- Since 2026-07-27 the sub-page tier spans every service-bearing subsystem: the eight `ctx` services without a page (permission presets, plan mode, runtime invariants, the HTTP carrier, storage, TUI extensions, workspaces, client modules) gained lean pages, so each harness service and event scope has exactly one owning subsystems page — the precondition for generating per-subsystem service/event reference into these pages instead of flat catalogs. +- Since 2026-07-27 the sub-page tier spans every service-bearing subsystem: the nine `ctx` services without a page (permission presets, plan mode, runtime invariants, the HTTP carrier, storage, TUI extensions, workspaces, client modules, telemetry) gained lean pages, so each harness service and event scope has exactly one owning subsystems page — the precondition for generating per-subsystem service/event reference into these pages instead of flat catalogs. diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index de644e8567..1e8473a373 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -58,4 +58,4 @@ Status: implemented - 主干与 seam 分界线是一个可复用的范围界定工具,而非一次性的:同一条「你编写/持有/接收的东西是核心;为其提供类型推导/渲染/持久化的机制是细节」规则,后来也被用于界定事件/服务目录的 harness 层与继承层分层。 - `ts type-equiv` 围栏是继 ` ```ts `(编译)和 ` ```ts ignore-check `(草稿)之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `(生成签名),复用了相同的跳过并排除处理。 - 添加或重塑核心类型现在附带一项文档义务,作者必须履行(门禁无法检测缺失的*新*类型),由 `dsh-code-review` 检查清单兜底。 -- 自 2026-07-27 起,子页面层级覆盖每个承载服务的子系统:原先没有页面的八个 `ctx` 服务(权限预设、计划模式、运行时不变式、HTTP 载体、存储、终端扩展、工作区、客户端模块)都获得了精简页面,于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面(而非平铺目录)的前提。 +- 自 2026-07-27 起,子页面层级覆盖每个承载服务的子系统:原先没有页面的九个 `ctx` 服务(权限预设、计划模式、运行时不变式、HTTP 载体、存储、终端扩展、工作区、客户端模块、遥测)都获得了精简页面,于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面(而非平铺目录)的前提。 diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml new file mode 100644 index 0000000000..a9a68fd065 --- /dev/null +++ b/docs/subsystems/client-modules.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md +client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c +client-modules.zh.md: bbcdb04cdce8a0d5256d2490deb238812e9a47fc diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md new file mode 100644 index 0000000000..bbcdb04cdc --- /dev/null +++ b/docs/subsystems/client-modules.zh.md @@ -0,0 +1,63 @@ +# Client 模块 + +[English](client-modules.md) | 中文 + +Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModuleHost`(`ClientModuleHostService`)形式提供。它扫描宿主 Loader 的 entry 找出 `dshClient` 包(package),组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins//client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[http-server.md](http-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。 + +源码:[`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts) + +## wire + +图是 Node 半与浏览器半之间的线上单源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。 + +```ts type-equiv +/** + * One composed client entry pushed by the host (web2 §0 graph row). Wire + * single source: the host node half (package root) produces this same shape. + * `immediately` marks stage-one prefetch; `inject` is informational graph + * metadata (the authoritative edges live in each package's dshClient + * declaration and reach fibers through entry creation). + */ +interface WebBootEntry { + /** Entry name == package name. */ + id: string + /** Bundle endpoint, '/plugins//client.js?rev='. */ + url: string + /** Bundle content hash (cache-busting consistency anchor). */ + rev: string + /** Package-name dependency edges, informational (preflight display / HMR diffing). */ + inject?: string[] + /** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */ + immediately?: boolean +} +``` + +```ts type-equiv +/** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */ +interface WebBootGraph { + /** Consistency anchor over the whole graph (content + bundle hashes). */ + rev: string + /** Composed entries; order carries no semantics (activation order is fiber inject waiting). */ + entries: WebBootEntry[] +} +``` + +每一行的 `rev` 是该 bundle 的内容哈希,并作为使缓存失效的查询参数附在 URL 上;图的 `rev` 对组合后的各行做哈希,因此任何一行的变化都会改变它。`immediately` 标记第一阶段预取档位(在模块面启动期间 fetch 并执行,只做工厂登记);惰性行在首次 import 时才拉取。 + +## 扫描 + +包加入这张表的方式,是在自己的 package.json 中声明 `dshClient`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。 + +扫描是单包增量的;不存在全量重扫代码路径。每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。 + +包元数据——包括「非 client 包」这一否定结论——按名缓存且永不过期:插件集合的变更在重启后生效。fiber 重启原样复用其行与 rev;bundle 内容变更只经 `rebuilt()` 到达图。 + +## 路由 + +`GET`/`HEAD /plugins//client.js` 以 `no-cache` 从磁盘提供已注册的 bundle(锚定一致性的是 rev 查询参数,而非 HTTP 缓存);其他方法返回 405。未知 id——或已注册、但 bundle 因尚未构建而不可读的行——回应一个大声的 404,而不是让载体的 SPA 回退把 HTML 当作 JavaScript 发出。index 转换在每次 index 渲染时注入当前图,因此刷新页面总是针对实时组合启动。 + +## 服务 + +`ClientModuleHostService`(`ctx.clientModuleHost`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](../cordis-catalog/services.md#ctxclientmodulehost--clientmodulehostservice)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回该 bundle 的绝对路径。`rebuilt(id)` 是 bundle 内容到达图的唯一入口:它对文件重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。 + +开发环境下,[dsh-client-hmr](../../packages/client/hmr/README.md) 是注册表的监视驱动:它的 Node 半从同步取得的基线出发,对图中每一行的 bundle 做 stat 轮询,变化时调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。生产环境的图完全不含 HMR(热模块替换)行;模块宿主自身从不监视文件。 diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 3057ee36ee..d0188cb2f3 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: 8f413a7a064ad6f63e0caec31354869e51139020 -core.zh.md: d0f02f0cfc2cd30fc67aacf5b17d1daf324295c5 +core.md: 1d0bf6c61500817860dfdd452bddc25adbbeffcc +core.zh.md: 06fd3b5a48d5267c9856c20589772047b6105b2c diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index bde64a39e4..1d0bf6c615 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -54,6 +54,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t | [tui.md](tui.md) | the terminal-extension seam: `TuiOverlayRequest`/`Host`/`Session`, close reasons and outcomes, the modal queue | | [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship | | [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap | +| [telemetry.md](telemetry.md) | the outbound reporting seam: `TelemetryRecord`/`TelemetrySeverity`, the `TelemetryBackend` contract, the `telemetry/record` redact waterfall | > Type declarations and their JSDoc on these pages are source-equivalent and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Ordinary blocks preserve complete declarations; `public-api` blocks preserve body-stripped public class declarations. Cordis services use the generated [service catalog](../cordis-catalog/services.md). diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 1f21785cad..06fd3b5a48 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -54,6 +54,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数 | [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 | | [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | | [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | +| [telemetry.md](telemetry.md) | 对外上报 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 契约、`telemetry/record` 脱敏 waterfall | > 这些页面上的类型声明及其 JSDoc 与源码等价,并由 `pnpm run verify-type-equiv` 检查漂移(见 [development.md](../development.md#documenting-types-verbatim-ts-type-equiv))。普通块保留完整声明;`public-api` 块保留去除实现体的公开 class 声明。Cordis 服务使用生成的[服务目录](../cordis-catalog/services.md)。 diff --git a/docs/subsystems/http-server.i18n.yaml b/docs/subsystems/http-server.i18n.yaml new file mode 100644 index 0000000000..cb2c734255 --- /dev/null +++ b/docs/subsystems/http-server.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/http-server.md +http-server.md: 565df72f2720a1211f50650d4b1bc8d6df4a007a +http-server.zh.md: 93b056648f044f6c79b85a6069deb3123f9a7a3e diff --git a/docs/subsystems/http-server.zh.md b/docs/subsystems/http-server.zh.md new file mode 100644 index 0000000000..fc5e18414b --- /dev/null +++ b/docs/subsystems/http-server.zh.md @@ -0,0 +1,49 @@ +# HTTP 服务器 + +[English](http-server.md) | 中文 + +[dsh-host-webserver](../../packages/host/webserver) 是 GUI 宿主 web 形态的 HTTP 载体:单个提供 `ctx.httpServer` 的 `node:http` 插件,由具名路由注册表加 index.html 转换挂点组成,兜底是静态 dist 回退。它不属于 agent loop(智能体循环)主干,也不是能力 seam:它不了解任何 harness 概念,每个功能表面(`/api` 桥接、插件 bundle、HMR(热模块替换)事件流)都是由其他插件注册的一条路由([分层说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md))。仅限 web(浏览器)形态:Electron 通过 `file://` 加载 dist,并经 IPC 桥接承载 fetch,不经过本服务器。 + +源码:[`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) + +## 路由 + +```ts type-equiv +/** Route match kind: 'exact' matches the pathname verbatim; 'prefix' p matches p and p/. */ +type WebRouteKind = 'exact' | 'prefix' +``` + +```ts type-equiv +/** One named route registration. */ +interface WebRoute { + kind: WebRouteKind + /** Absolute pathname, no trailing slash. */ + path: string + /** Owns the full response lifecycle (may hold the response open, e.g. SSE). */ + handler: (req: IncomingMessage, res: ServerResponse) => void | Promise +} +``` + +匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到静态 dist 回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,启动窗口期内尚未被认领的请求全部由回退应答。回退保持锁定语义:非 GET/HEAD 返回 405,越出 dist 根目录的路径穿越返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 交付([`static.ts`](../../packages/host/webserver/src/static.ts))。 + +## 配置 + +```ts type-equiv +/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */ +interface Config { + /** Listen host; the two supported values are loopback and all-interfaces. */ + host: '127.0.0.1' | '0.0.0.0' + /** Listen port; zero requests an OS-assigned port. */ + port: number + /** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */ + distIndex: string +} +``` + +`host` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。`distIndex` 是组合应用解析后注入的组装事实。 + +## 服务 + +`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会从 init 抛出,形成一个 FAILED fiber,由启动阶段的大声失败巡检报告。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层面的契约,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,`config.port` 为 0 时读到的是操作系统分配的值。 + +处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能保持响应打开(如 SSE(Server-Sent Events)),而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。 diff --git a/docs/subsystems/invariants.i18n.yaml b/docs/subsystems/invariants.i18n.yaml new file mode 100644 index 0000000000..dc5a8a6d0d --- /dev/null +++ b/docs/subsystems/invariants.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/invariants.md +invariants.md: 143a7feabbab18556f595a78c7aef17caa6d3028 +invariants.zh.md: ab7aa0221101400d552ff47e1cfa3275d99a0b6d diff --git a/docs/subsystems/invariants.zh.md b/docs/subsystems/invariants.zh.md new file mode 100644 index 0000000000..ab7aa02211 --- /dev/null +++ b/docs/subsystems/invariants.zh.md @@ -0,0 +1,59 @@ +# 运行时不变式 + +[English](invariants.md) | 中文 + +[dsh-invariants](../../packages/support/invariants) 是面向包(package)自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 伴随插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定;seam 设计由[不变式服务 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md)规定。 + +源码:[`packages/support/invariants/src/index.ts`](../../packages/support/invariants/src/index.ts) + +## 选择 + +```ts type-equiv +/** Runtime invariant selection configured on the service plugin. */ +interface Config { + /** Global switch; defaults to `true`. */ + readonly enabled?: boolean + /** Case-sensitive JavaScript regex sources that admit package names; empty admits all. */ + readonly package_allowlist?: string[] + /** Case-sensitive JavaScript regex sources that exclude package names after allowlist matching. */ + readonly package_blocklist?: string[] +} +``` + +一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时大声失败:空白、带空白填充、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/support/invariants/README.md))。 + +## 安装器 + +```ts type-equiv +/** + * Throw a package-attributed invariant failure. + * @param message - violated package contract without the standard prefix. + * @returns never because reporting a violation throws. + */ +type InvariantFailure = (message: string) => never +``` + +```ts type-equiv +/** Install one package's checks into the registration's child context. */ +interface InvariantInstaller { + /** + * Install the package contribution. + * @param ctx - child context owned by this invariant registration. + * @param fail - reporter bound to the registering package name. + * @returns nothing, or a promise settling after asynchronous checks finish. + */ + (ctx: Context, fail: InvariantFailure): void | Promise + /** Services the child installer fiber may access. */ + readonly inject?: Inject +} +``` + +被启用的安装器在专属的子 Cordis fiber 中运行;`installer.inject` 声明该 fiber 可以访问的服务,注册成功之前会先等待安装器同步或异步地执行完毕。`fail(message)` 抛出 `InvariantError`(`extends Error`,带稳定的 `code: 'INVARIANT'`、所属 `packageName`,以及前缀为 `invariant violated by "": …` 的消息),因此违规可归因,而注册表无需导入任何产品包。 + +## 服务 + +`ctx.invariants.register(packageName, installer)` 为完整 npm 包名保留唯一一个活跃注册,并返回其绑定到 effect 的 disposer。即使过滤器使安装器保持不活跃,保留依然成立,因此两个插件绝不可能静默认领同一个包名;重复、空白或含空白字符的名称会抛出异常。安装器失败会原子地 dispose(资源释放)子 fiber 并释放保留。服务拥有每个注册 fiber,而返回的 disposer 同时属于伴随插件的 fiber:卸载任一侧都会移除监听器、trace 状态和保留项,因此伴随插件可以重载并再次注册同一名称,不留残余状态。 + +## 伴随插件契约 + +每个工作区包都拥有一个 `./invariant` 伴随插件([包契约](../../packages/AGENTS.md));发布与注册是穷尽式的,但断言刻意不为凑数而虚构。只有当包对某个可观察事件或可变数据拥有所有权关系时,伴随插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行伴随插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。 diff --git a/docs/subsystems/permission.i18n.yaml b/docs/subsystems/permission.i18n.yaml new file mode 100644 index 0000000000..f800d4ff08 --- /dev/null +++ b/docs/subsystems/permission.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/permission.md +permission.md: 49dd51ff86d0dbce26fca0ed21527f71bb144dec +permission.zh.md: 5fb93e9f923582e6c530bac4a22e2ce62cff156d diff --git a/docs/subsystems/permission.zh.md b/docs/subsystems/permission.zh.md new file mode 100644 index 0000000000..5fb93e9f92 --- /dev/null +++ b/docs/subsystems/permission.zh.md @@ -0,0 +1,63 @@ +# 权限预设 + +[English](permission.md) | 中文 + +[dsh-permission](../../packages/ui/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮,即[沙箱模式](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)负责决策依据。 + +源码:[`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts) + +## 预设表 + +预设是一个表键,映射到一组沙箱/审批组合,外加可选的客户端展示信息;默认预设表自带 `workspace-write`(`workspace-write` + `ask`)和 `danger-full-access`(`danger-full-access` + `never`)。 + +```ts type-equiv +/** One preset's sandbox/approval bundle and optional client presentation. */ +interface PresetSpec { + /** The `sandbox/mode` value the preset writes through. */ + sandbox: SandboxMode + /** The `approval/policy` value the preset writes through. */ + approval: ApprovalPolicy + /** The display label a client shows for this preset; the raw table key when omitted. */ + name?: string + /** One user-facing sentence on what the preset means; omitted when not configured. */ + description?: string +} +``` + +```ts type-equiv +/** The {@link PermissionService} config: the deployment's preset table. */ +interface Config { + /** + * The preset table: name → knob bundle. Defaults to `workspace-write` + * (workspace-write + ask) and `danger-full-access` (danger-full-access + + * never). The name `custom` is reserved for the derived not-a-preset state. + */ + presets?: Record +} +``` + +该服务要求一个施加沙箱隔离的 `ctx.bash` 执行器和 `ctx.approval`,配置错误在插件加载时即失败:名为 `custom` 的表项会抛出异常(该名称保留给派生的「非预设」状态);在不施加隔离的 bash 执行器(没有 `sandboxMode` 能力事实)之上组合同样抛出异常,因为预设捆绑了一个沙箱模式。 + +## 当前预设与派生的 `custom` + +`current(events)` 从旋钮派生实际生效的预设,而不是只看自身事件:它折叠会话的生效沙箱模式(回退到执行器配置的模式)与生效审批策略(先回退到审批服务配置,再回退到 `ask`),优先取仍然匹配的已记录选择,其次取声明顺序中第一个匹配的表项,否则返回 `CUSTOM_PRESET`(`'custom'`)。`custom` 只是派生值:客户端可以把它显示为当前值,但它绝不是切换目标,也绝不出现在事件 payload 中。 + +`names` 按预设表声明顺序列出可切换的预设;`optionOf(name)` 为某个表键(label 回退为该键)或 `custom` 构建客户端渲染的选项,传入其他任何名称都会抛出异常。 + +```ts type-equiv +/** The select-option shape a presentation layer advertises for one preset (or for the derived `custom` state). */ +interface PresetOption { + /** Stable option value: the table key, or `custom`. */ + value: string + /** The display label. */ + name: string + /** One user-facing sentence on what the value means. */ + description?: string +} +``` + +## 切换与 `permission/preset` 事件 + +`set(session, name)` 解析预设(未知名称抛出异常),在 `name` 尚不是生效预设时追加一条仅记日志的 `permission/preset` 事件,然后通过各旋钮自己的 setter([dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) 的 `setSandboxMode` 与 [dsh-user-approval](../../packages/ui/user-approval) 的 `setApprovalPolicy`)写入,且仅当该旋钮的生效值发生变化时才写。同一轮次内,选择事件先于旋钮事件出现;重新选择当前生效的预设则什么都不追加。 + +`permission/preset` 是持久、仅记日志的用户意图:它不进入模型 transcript(文本记录),模型可见的后果由旋钮事件经各自消费方承担;它存在是为了在两个预设共享同一个旋钮组合时,让 `current()` 仍能保住用户选择的究竟是哪一个预设;`effectivePermissionPreset(events)` 折叠最后一条,回放不需要任何追赶状态。完整事件声明见[持久化日志事件目录](../persistence-catalog.md);方法签名见生成的[服务目录](../cordis-catalog/services.md#ctxpermission--permissionservice)。 diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml new file mode 100644 index 0000000000..3ae156615f --- /dev/null +++ b/docs/subsystems/plan.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/plan.md +plan.md: f67e7734ca3fd55c284d3d41227f8241de7b9f5f +plan.zh.md: 565cf2016636acb6c26d20c731b9e6e374cee1da diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md new file mode 100644 index 0000000000..af21a6d027 --- /dev/null +++ b/docs/subsystems/plan.zh.md @@ -0,0 +1,39 @@ +# 计划模式 + +[English](plan.md) | 中文 + +计划模式是 [dsh-plan-mode](../../packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeService`):激活期间,一段部署持有的指引段落会影响每个模型请求。它是**软性指引**,有意独立于[沙箱模式](sandbox.md)与[审批策略](approval.md)这两条强制执行轴:那些旋钮(knob)从不读写计划状态,需要硬边界的部署另行组合两者。该包(package)是一项可选能力,不属于 agent loop(智能体循环)主干;它的对外表面是 `plan:policy` 提示词段落、始终保持注册的 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)负责决策依据;[包 README](../../packages/plan/plan-mode/README.md) 负责模型体验与限制细节。 + +源码:[`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/src/index.ts) + +## 已记录状态与恢复 + +`plan/mode`(`{ active: boolean }`)是仅记日志、整值替换的[会话事件](session.md):持久且可回放,绝不进入模型 transcript(文本记录)。`foldPlanMode(events, end?)` 返回前缀中最后一条已记录值,没有时返回 `false`:生效状态始终是会话日志的纯折叠,因此恢复、fork 与压缩(compaction)无需实时镜像即可将其复原,UI 通过 `session/event` 观察已提交的切换。完整事件声明见[持久化日志事件目录](../persistence-catalog.md)。 + +## 待定意图与轮次边界冲刷 + +由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到轮次边界。`set(agent, active)` 记录待定选择(目标值与已记录或已在待定中的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`,即影响当前步骤的已记录状态,加上正在等待边界的乐观选择。 + +服务会在三类边界处、于受影响的请求组装之前冲刷一条待定选择:提示词提交、普通轮次延续和请求恢复重试。冲刷在下游监听器链之后运行,因此在某个异步监听器等待期间到达的选择,仍会影响该边界紧接其后的那个请求。冲刷失败会被隔离(计划策略绝不能阻塞提示词或轮次),追加失败的选择保持待定,等待后续边界。已冲刷的用户选择还会以一条插件来源的 `user/message` 通知叙述这次切换,但仅当最后记录的请求头描述的是另一种状态时才叙述,因此模型恰好在上下文变化时被告知,且绝不重复。空闲时做出的待定选择只存在于进程内,进程在下一个边界之前退出即丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。 + +## 配置 + +```ts type-equiv +/** Deployment-owned plan guidance. */ +interface PlanModeConfig { + /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */ + section: string +} +``` + +`section` 缺失、为空白或不是字符串,以及任何未知键,都会在插件加载时失败,而不是静默地什么都不影响。计划模式激活期间,确切的 `section` 文本以 order 50 渲染为 `plan:policy` [系统提示词段落](system-prompt.md);未激活的计划模式不贡献任何文本。 + +## 退出工具与 `/plan` 命令 + +[`exit_plan_mode`](../tool-catalog.md#deepseek-aidsh-plan-mode) 在计划模式未激活时仍保持注册,因此跨越边界只改变提示词段落,绝不改变请求的工具目录;在计划模式之外执行会失败。在计划模式中,它要求一份以 `#` 标题开头的完整 markdown 计划,并通过[用户交互 seam](user-interaction.md) 呈交评审。批准返回 `{ approved: true }`,并记录一个静默(不叙述)的待定退出,在该步骤之后冲刷:计划指引在 assistant 本批工具调用的剩余部分继续生效,而工具结果本身叙述这次转换。「继续规划」则是一次携带用户反馈的失败调用,模型据此修订并再次呈交;评审期间交互通道缺失或服务重载同样使调用失败,而不是静默离开计划模式。 + +当 [`ctx.commands`](commands.md) 被组合时,插件注册 `/plan [off|message]`:单独的 `/plan` 选择计划模式;任何其他非空消息先选择计划模式,再通过 `agent.steer()` 提交该文本,使其在计划指引下成为下一步骤的普通已记录用户消息;确切参数 `off` 选择未激活,这还会在计划模式尚未进入任何请求之前,取消尚未冲刷的待定条目。 + +## 服务 + +`ctx.planMode` 拥有已记录的计划状态、边界处的应用与叙述、`plan:policy` 段落、`/plan` 命令和稳定注册的退出工具;`get`/`set` 签名见生成的[服务目录](../cordis-catalog/services.md#ctxplanmode--planmodeservice)。 diff --git a/docs/subsystems/storage.i18n.yaml b/docs/subsystems/storage.i18n.yaml new file mode 100644 index 0000000000..92387332f0 --- /dev/null +++ b/docs/subsystems/storage.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/storage.md +storage.md: 5d54d7337b5ef52e24179758130b9fa8cd196376 +storage.zh.md: c575f989fe4b3536ce3c8d3b3af08ebda418166d diff --git a/docs/subsystems/storage.zh.md b/docs/subsystems/storage.zh.md new file mode 100644 index 0000000000..c575f989fe --- /dev/null +++ b/docs/subsystems/storage.zh.md @@ -0,0 +1,125 @@ +# 存储 + +[English](storage.md) | 中文 + +存储子系统持久保存一切不属于会话事件日志的数据(会话日志有自己的 seam——见 [persistence.md](persistence.md))。它是一项可选能力,不属于 agent loop(智能体循环)主干,并按[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)拆分:枢纽(hub)与后端接口([dsh-storage](../../packages/storage/storage),`ctx.storage`)、后端实现(注册为 `json` 的 [dsh-storage-json](../../packages/storage/storage-json) 与注册为 `sqlite` 的 [dsh-storage-sqlite](../../packages/storage/storage-sqlite)),以及领域数据形式([dsh-storage-domain](../../packages/storage/storage-domain),`ctx.storageDomain`,也可经 `ctx.storage.domain` 访问)——它是后端 seam 的唯一消费方,也是其他一切所使用的类型化 API。枢纽自身不做任何 IO:后端拥有介质,数据形式拥有语义,产品包(package)绝不直接触碰后端。设计记录:[领域 KV 存储 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)。 + +源码:[`packages/storage/storage/src/backend.ts`](../../packages/storage/storage/src/backend.ts) · [`packages/storage/storage-domain/src/spec.ts`](../../packages/storage/storage-domain/src/spec.ts) · [`packages/storage/storage-domain/src/events.ts`](../../packages/storage/storage-domain/src/events.ts) + +## 枢纽:`ctx.storage` + +`Storage`([签名](../cordis-catalog/services.md#ctxstorage--storage))是汇合点,不是存储本体。`ctx.storage.backend` 是一张名称 → 后端的表:多个后端并排保持挂载,哪个后端服务哪个消费方由该消费方自己的配置决定(即领域层的路由表),绝不是枢纽全局的选择。`register(name, backend)` 返回 disposer;重复名称与查找未知名称都抛出 `StorageError`。dispose(资源释放)只注销名称——由拥有插件在注销之后自行关闭后端。每个后端插件还会发布一个仅用于生命周期的服务键(`storageBackendServiceKey(name)`),数据形式提供方注入它,使自身激活不会与后端注册发生竞态。 + +数据形式以一张可合并扩展的键 map 挂载到枢纽上: + +```ts type-equiv +/** + * Data forms mountable on the hub, keyed by form name. Form owners extend + * this map via declaration merging (the domain layer merges + * `domain: DomainFacility`) and mount the facility in their `apply`. + */ +interface StorageForms {} +``` + +`mount(form, facility)` 是一个 effect,其 disposer 负责卸载;对同一键的第二次挂载抛出 `duplicate-mount`。`form(form)` 解析已挂载的 facility,在拥有插件加载之前抛出 `form-not-mounted`——组合方应据此安排插件顺序,而不是静默推迟。领域层合并 `domain: DomainFacility`,因此 `ctx.storage.domain` 与 `ctx.storageDomain` 是同一个对象。 + +## 后端 seam + +```ts type-equiv +/** + * One registered backend. A backend owns exactly one medium and shares its + * lifecycle across all facets; facets are optional members — a backend that + * cannot serve a shape simply omits it, and resolution fails loud instead. + */ +interface StorageBackend { + /** Key-value data shape; absent when this backend cannot serve it. */ + readonly kv?: KvFacet + + /** + * Drain in-flight writes across all open units and release the medium. + * Idempotent; concurrent and repeated calls resolve once teardown finishes. + * @returns resolution after the medium is released. + */ + close(): Promise +} +``` + +一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并暴露可选的数据形状 facet;目前 `kv` 是唯一的 facet。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、格式版本、表名清单,以及是否存在全局单例槽位——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。介质上记录的版本与之不同时拒绝 `version-mismatch`;无法按该 unit 解析的介质拒绝 `malformed-medium`(不做迁移:预发布立场)。[`backend.ts`](../../packages/storage/storage/src/backend.ts) 是逐条款的规范性契约,[`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) 中的共享一致性套件对每个后端断言其中每一条款。[json 后端](../../packages/storage/storage-json/README.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](../../packages/storage/storage-sqlite/README.md)在单个数据库中按一行一文档存储,是高频更新领域的路由选择。 + +## 声明领域 + +领域由其拥有包声明一次,形式是一个 spec 对象——它是该领域的身份、布局和记录 schema 的单一来源(schema 用 zod 编写,因此 `z.infer` 让消费方类型无需重复声明): + +```ts type-equiv +/** Static declaration of one domain: identity, version, and record layout. */ +interface DomainSpec { + /** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */ + readonly name: string + /** Domain format version; a medium stamped with a different version rejects at open. */ + readonly version: number + /** Optional global singleton slot. */ + readonly global?: DomainGlobalSpec + /** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */ + readonly tables: Record +} +``` + +`defineDomain(spec)` 固定 spec 的字面量类型,并在拥有方的模块加载时、任何介质被触碰之前就大声失败:领域名或表名不匹配 `UNIT_NAME_RE`、版本不是非负整数、global schema 接受 `null`,这些都会抛出(`null` 是介质的「从未写入」哨兵值,可空的 global 一旦存储就无法往返还原)。`domainTable(schema)` 声明一张表,其键类型是编译期的幻影类型(通常是[品牌化 id](core.md#branded-ids));`descriptorOf(spec)` 投影出面向后端的 unit 描述符。 + +## 打开的领域 + +```ts type-equiv +/** One open domain, typed by its spec. */ +interface Domain { + /** Domain name from the spec. */ + readonly name: string + /** Global singleton handle; a spec without `global` has no usable handle (`never`). */ + readonly global: DomainGlobalHandleOf + /** + * Resolve one declared table handle. Handles are stable — repeated calls + * return the same instance. + * @param name - Declared table name. + * @returns the typed table handle. + */ + table(name: N): KvTable, TableValueOf> + + /** + * Close this domain: reject new writes immediately, drain already-queued + * writes (their events still emit), release the backend unit, then free + * the domain name for a later open. Idempotent — repeated calls share one + * teardown. The consumer owns this call (typically as its own `ctx.effect` + * disposer); the facility closes any domain left open when it unmounts. + * @returns resolution after the unit is released. + */ + close(): Promise +} +``` + +读取是同步的,来自权威的内存态:`KvTable` 暴露 `get`/`entries`/`keys`/`size`(快照迭代器,在排队写入落地期间保持稳定),global 句柄的 `get()` 在第一次 `set` 将槽位物化到介质之前一直返回 spec 的 `initial`。每次写入——`put`、`delete`、`update`、`global.set`——都在同一条逐领域写链上排队,先到达后端持久性,再更新内存,最后发出 `domain/changed`;后端写入被拒时内存原样不动,因此读取绝不会偏离介质。`update(key, fn)` 在其写链槽位上是一次原子的读-改-写(键缺失时拒绝 `missing-key`);`delete` 一个不存在的键 resolve 为 `false`,不产生写入也不产生事件。返回的记录就是存储的对象本身,不是副本——请经 `put`/`update` 整体替换,绝不要就地修改。 + +## 领域 facility:`ctx.storageDomain` + +`DomainFacility`([签名](../cordis-catalog/services.md#ctxstoragedomain--domainfacility))在经过路由的后端之上打开已声明的领域。路由是领域插件的配置,绝不属于枢纽:`backend` 指定必填的默认路由,`routes` 按领域名逐个覆盖。`open(spec)` 逐步执行,任一步失败都使整个调用失败:拒绝已打开或仍在关闭中的名称(`already-open`),解析路由(`backend-not-found`),要求后端具备 `kv` facet(`facet-unsupported`),打开 unit(后端的 `version-mismatch`/`malformed-medium` 原样透传),并按 spec 的 zod schema 校验每条已存储记录和 global(`invalid-record`,附带出错的表与键)。调用方拥有返回的句柄,并用 `Domain.close()` 释放它;插件卸载时仍处于打开状态的领域由 facility 负责关闭,已关闭领域的名称只有在拆除完全结束后才释放出来供重新打开。`get(name)` 是无类型的诊断查找;`closeAll()` 是卸载路径。 + +## 变更事件:`domain/changed` + +每次持久写入都发出一个事件,严格发生在后端确认持久性之后,顺序遵循该领域的写链([事件条目](../cordis-catalog/events.md#domainchanged--emit)): + +```ts type-equiv +/** Shared location fields of one durable domain change. */ +interface DomainChangedBase { + /** Owning domain name. */ + readonly domain: string + /** Table name; `''` for a global-singleton write. */ + readonly table: string + /** Record key; `''` for a global-singleton write. */ + readonly key: string +} +``` + +```ts type-equiv +/** One durable domain change; a closed union — switch on `operation`. */ +type DomainChanged = DomainChangedPut | DomainChangedDeleted +``` + +`put`(插入、覆写和 global 写入)在 `value` 中携带新快照——绝不携带旧值;需要做差异比较的消费方自行保留上一份快照。`deleted` 是不携带值的墓碑。该事件是通知,不是事务参与者:发出时提交点已经过去,因此同步抛出的监听器会被兜住并记录一条警告,而不会让已经持久的写入被拒绝;发出的值等于发出时刻的内存态。该事件仅限进程内;跨进程的变更推送是延后工作,记录在[包 README](../../packages/storage/storage-domain/README.md)中。 diff --git a/docs/subsystems/telemetry.i18n.yaml b/docs/subsystems/telemetry.i18n.yaml new file mode 100644 index 0000000000..9f6fdb3af3 --- /dev/null +++ b/docs/subsystems/telemetry.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md +telemetry.md: 4942a402dacb40ed64070c2b7824bfea7a14c872 +telemetry.zh.md: c99b287fd4cb4e9bc585ffe365ca08df37f6ccd1 diff --git a/docs/subsystems/telemetry.md b/docs/subsystems/telemetry.md new file mode 100644 index 0000000000..4942a402da --- /dev/null +++ b/docs/subsystems/telemetry.md @@ -0,0 +1,109 @@ +# Telemetry + +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). + +Source: [`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts) + +## The logical record + +```ts type-equiv +/** + * Severity of a telemetry record, pre-mapped at capture so a receiver can + * alert with zero configuration: `error` for events whose own outcome flag + * says so (`tool/result.isError`, `turn/end` error reasons) and for + * `agent-error` operational records. Captured events otherwise default to + * `info`; `warn` remains available to `telemetry/record` policies and + * backends. + */ +type TelemetrySeverity = 'info' | 'warn' | 'error' +``` + +```ts type-equiv +/** + * One logical record handed to a backend — the seam's whole outbound + * vocabulary. Ledger records mirror session-log events one-to-one; + * operational records (`channel: 'ops'`) carry the two signals with no log + * home (`agent-error`, `shutdown`) and deliberately omit `event.seq`-style + * identity so they can never be mistaken for ledger rows. + */ +interface TelemetryRecord { + /** Ledger (session-log mirror) or ops (operational signal) channel; backends keep the two under separate instrumentation scopes. */ + channel: 'ledger' | 'ops' + /** Unix epoch milliseconds — the source event's append time for ledger records, the emission time for ops records. */ + time: number + /** Pre-mapped alerting severity; see {@link TelemetrySeverity}. */ + severity: TelemetrySeverity + /** + * Identity attributes, deliberately minimal: ledger records carry + * `session.id`, `event.type`, `event.seq`, plus `session.cwd` / + * `session.parent_id` when the header has them; ops records carry + * `telemetry.op`, `session.id`, and (for `agent-error`) `agent.id`, + * `turn`, `step`, `error.name`. Anything recoverable from the body is + * intentionally NOT duplicated here. + */ + attributes: Record + /** + * The complete payload: a deep copy of the session event's `data` for + * ledger records (JSON-serializable by `Session.append`'s own + * validation), or the op payload for ops records. Never mutated after + * handoff. + */ + body: unknown +} +``` + +Only the first `assistant/chunk` of each `(turn, step)` ships — the stream-started signal; the rest drop at capture, so `seq` gaps are routine on the wire and never a loss signal. Every other [session event](session.md) type, including plugin-merged ones the seam never heard of, passes through whole. Delivery is at-most-once downstream of the handoff; receivers dedupe on `(session.id, event.seq)`. + +## The backend contract + +```ts type-equiv +/** + * The backend contract the coordinator hands records to — the minimum any + * reporting SDK satisfies with zero bending. {@link Telemetry} is its + * service-registered form; tests compose the coordinator with a bare + * implementation of this interface. + */ +interface TelemetryBackend { + /** + * Hand one record to the backend's pipeline. MUST be a non-blocking + * enqueue — the coordinator calls this synchronously from the + * `session/event` hot path, so anything slower than a queue push would tax + * the agent loop. Errors thrown here are contained by the coordinator and + * logged; they never reach the loop. + * @param record - the logical record to report; owned by the backend after the call. + */ + emit(record: TelemetryRecord): void + /** + * Optional hint that a natural boundary (turn end) passed — a backend may + * forward it to its SDK's flush so records land at turn boundaries. Called + * fire-and-forget; implementations must not block and must not throw + * meaningfully (the coordinator contains exceptions). Most backends should + * leave this unimplemented and let their SDK's own batching cadence govern + * export timing: a backend that does implement it owns the interaction + * between its concurrent flushes and {@link shutdown}'s drain (the OTel + * backend removed its implementation for exactly that hazard — see the + * revival Agent Note). + */ + flush?(): void + /** + * Forward the fiber's disposal to the SDK: flush whatever is queued and + * reach quiescence, per the SDK's own shutdown contract. Everything + * emitted before this call must still be delivered — including records + * enqueued while a {@link flush} hint is in flight, so a backend whose SDK + * guards against concurrent flushes orders behind the outstanding one (the + * coordinator emits its dispose-time `shutdown` markers immediately before + * calling this). Awaited by the coordinator's dispose; a rejection is + * logged as a warning and never fails application teardown. + * @returns resolves when the backend's pipeline has quiesced. + */ + shutdown(): Promise +} +``` + +`Telemetry` (`ctx.telemetry`, [signatures](../cordis-catalog/services.md#ctxtelemetry--telemetry-abstract-seam)) is the contract's loadable form — one implementation per context, duplicate load throws — and a backend composes the seam's `TelemetryCoordinator` in its constructor to install the capture side. + +## The redact waterfall: `telemetry/record` + +Every record passes the `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics) between projection and `emit()` ([event entry](../cordis-catalog/events.md#telemetryrecord--waterfall)). The seam ships NO rules of its own: with no listener mounted, records reach the backend exactly as captured, so exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath; a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only — the canonical session log is never rewritten. diff --git a/docs/subsystems/telemetry.zh.md b/docs/subsystems/telemetry.zh.md new file mode 100644 index 0000000000..c99b287fd4 --- /dev/null +++ b/docs/subsystems/telemetry.zh.md @@ -0,0 +1,109 @@ +# 遥测(telemetry) + +[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)。 + +源码:[`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts) + +## 逻辑记录 + +```ts type-equiv +/** + * Severity of a telemetry record, pre-mapped at capture so a receiver can + * alert with zero configuration: `error` for events whose own outcome flag + * says so (`tool/result.isError`, `turn/end` error reasons) and for + * `agent-error` operational records. Captured events otherwise default to + * `info`; `warn` remains available to `telemetry/record` policies and + * backends. + */ +type TelemetrySeverity = 'info' | 'warn' | 'error' +``` + +```ts type-equiv +/** + * One logical record handed to a backend — the seam's whole outbound + * vocabulary. Ledger records mirror session-log events one-to-one; + * operational records (`channel: 'ops'`) carry the two signals with no log + * home (`agent-error`, `shutdown`) and deliberately omit `event.seq`-style + * identity so they can never be mistaken for ledger rows. + */ +interface TelemetryRecord { + /** Ledger (session-log mirror) or ops (operational signal) channel; backends keep the two under separate instrumentation scopes. */ + channel: 'ledger' | 'ops' + /** Unix epoch milliseconds — the source event's append time for ledger records, the emission time for ops records. */ + time: number + /** Pre-mapped alerting severity; see {@link TelemetrySeverity}. */ + severity: TelemetrySeverity + /** + * Identity attributes, deliberately minimal: ledger records carry + * `session.id`, `event.type`, `event.seq`, plus `session.cwd` / + * `session.parent_id` when the header has them; ops records carry + * `telemetry.op`, `session.id`, and (for `agent-error`) `agent.id`, + * `turn`, `step`, `error.name`. Anything recoverable from the body is + * intentionally NOT duplicated here. + */ + attributes: Record + /** + * The complete payload: a deep copy of the session event's `data` for + * ledger records (JSON-serializable by `Session.append`'s own + * validation), or the op payload for ops records. Never mutated after + * handoff. + */ + body: unknown +} +``` + +每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。交接下游的投递为至多一次(at-most-once);接收端基于 `(session.id, event.seq)` 去重。 + +## 后端契约 + +```ts type-equiv +/** + * The backend contract the coordinator hands records to — the minimum any + * reporting SDK satisfies with zero bending. {@link Telemetry} is its + * service-registered form; tests compose the coordinator with a bare + * implementation of this interface. + */ +interface TelemetryBackend { + /** + * Hand one record to the backend's pipeline. MUST be a non-blocking + * enqueue — the coordinator calls this synchronously from the + * `session/event` hot path, so anything slower than a queue push would tax + * the agent loop. Errors thrown here are contained by the coordinator and + * logged; they never reach the loop. + * @param record - the logical record to report; owned by the backend after the call. + */ + emit(record: TelemetryRecord): void + /** + * Optional hint that a natural boundary (turn end) passed — a backend may + * forward it to its SDK's flush so records land at turn boundaries. Called + * fire-and-forget; implementations must not block and must not throw + * meaningfully (the coordinator contains exceptions). Most backends should + * leave this unimplemented and let their SDK's own batching cadence govern + * export timing: a backend that does implement it owns the interaction + * between its concurrent flushes and {@link shutdown}'s drain (the OTel + * backend removed its implementation for exactly that hazard — see the + * revival Agent Note). + */ + flush?(): void + /** + * Forward the fiber's disposal to the SDK: flush whatever is queued and + * reach quiescence, per the SDK's own shutdown contract. Everything + * emitted before this call must still be delivered — including records + * enqueued while a {@link flush} hint is in flight, so a backend whose SDK + * guards against concurrent flushes orders behind the outstanding one (the + * coordinator emits its dispose-time `shutdown` markers immediately before + * calling this). Awaited by the coordinator's dispose; a rejection is + * logged as a warning and never fails application teardown. + * @returns resolves when the backend's pipeline has quiesced. + */ + shutdown(): Promise +} +``` + +`Telemetry`(`ctx.telemetry`,[签名](../cordis-catalog/services.md#ctxtelemetry--telemetry-abstract-seam))是该契约的可加载形态:每个上下文只允许一个实现,重复加载会抛出异常;后端在其构造函数中组合 seam 的 `TelemetryCoordinator`,以此装配捕获侧。 + +## 脱敏 waterfall:`telemetry/record` + +每条记录在投影与 `emit()` 之间都要经过 `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics)([事件条目](../cordis-catalog/events.md#telemetryrecord--waterfall))。seam 自身不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。 diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml new file mode 100644 index 0000000000..5540d00aab --- /dev/null +++ b/docs/subsystems/workspace.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/workspace.md +workspace.md: 2726115457905687e3c2cfc35a7a3f7251e6347c +workspace.zh.md: 190c44b2d73f9e123242c34b5429839abe3b9297 diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md new file mode 100644 index 0000000000..190c44b2d7 --- /dev/null +++ b/docs/subsystems/workspace.zh.md @@ -0,0 +1,121 @@ +# 工作区 + +[English](workspace.md) | 中文 + +工作区(workspace)是用户工作目录的持久记录:一个建立在规范路径之上的稳定 id、一个显示标题,以及归属于它的会话的有序账本。该子系统是单个包(package)([dsh-workspace](../../packages/workspace/workspace),`ctx.workspace`)——一项宿主侧可选能力,不属于 agent loop(智能体循环)主干,并且对模型不可见(没有工具、没有提示词文本、没有会话事件)。它通过[存储领域数据形式](storage.md)存储自己的记录,并对照 [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log) 校验会话成员资格,因此 `storageDomain` 与 `sessionPersistence` 是必需的启动依赖:持久化这一侧不可用时插件保持 pending,而不会被误认为历史为空。设计记录:[领域 KV 存储 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md);引导与 GUI 顺序:[Workspace UI 产品流程 Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md)。 + +源码:[`packages/workspace/workspace/src/types.ts`](../../packages/workspace/workspace/src/types.ts) + +## 标识 + +```ts type-equiv +/** + * Identifies one workspace record. A generated uuid, never the path: path + * normalization rewrites paths, and a reference anchor must stay stable. + */ +type WorkspaceId = Branded<'WorkspaceId'> +``` + +`WorkspaceId` 是[品牌化 id](core.md#branded-ids)。路径标识与之分离:`realpathNormalize`(`fs.realpath`;尾部斜杠、`..` 与符号链接全部解析)是唯一的一套唯一性规范——工作区路径以规范化形式存储,唯一性即规范路径的字符串相等(指向已被拥有目录的符号链接会与之冲突),attach 时的会话 cwd 检查也走同一套规范。 + +## 工作区实体 + +消费方只看到 `Workspace` 接口;实现保持包内私有。 + +```ts type-equiv +/** + * One workspace: a stable id over an existing directory, a display title, and + * an ordered candidate account of sessions. Membership requires both an id in + * that account and a session header whose canonical cwd equals the workspace + * path. Consumers only see this interface; the implementation stays private. + */ +interface Workspace { + /** Stable record id (generated uuid). */ + readonly id: WorkspaceId + + /** + * Canonical directory path: the `fs.realpath` of the path given at create + * time (trailing slashes, `..`, and symlinks all resolved). Never rewritten + * afterwards, even when the directory disappears (see {@link status}). + */ + readonly path: string + + /** Display title. Defaults to `basename(path)` at create; duplicates are allowed. */ + readonly title: string + + /** ISO-8601 creation instant, stamped at create and never rewritten. */ + readonly createdAt: string + + /** ISO-8601 instant of the last durable mutation (create counts as one). */ + readonly updatedAt: string + + /** + * Header-validated sessions in manually owned order: a new session is + * prepended at attach, explicit reordering goes through + * `insertSessionBefore`, and activity never reorders. The durable candidate + * account is filtered synchronously: missing headers, invalid cwd values, + * and canonical cwd mismatches are never returned. A subsequent workspace + * mutation prunes those filtered candidates durably. + */ + readonly sessionIds: readonly SessionId[] + + /** + * Replace the display title durably. + * @param title - New title; any string, duplicates across workspaces allowed. + * @returns resolution after durability. + */ + setTitle(title: string): Promise + + /** + * Prepend a session to this workspace's candidate account. An already + * accounted id resolves without writing. A new id's live or persisted + * header cwd must resolve to an existing directory equal to {@link path}; + * unknown ids, missing or invalid cwd values, and mismatches reject without + * writing. + * @param sessionId - The session to record. + * @returns resolution after durability. + */ + attachSession(sessionId: SessionId): Promise + + /** + * Move an accounted session within the manual order, DOM-insertBefore-like: + * with an anchor the session lands before it, without one it appends to the + * end. Only the moved id changes position. A session or anchor absent from + * the account rejects without writing; a move to the current position + * resolves without writing (decided on the domain write chain). + * @param sessionId - The accounted session to move. + * @param beforeSessionId - Accounted anchor to insert before; omitted appends. + * @returns resolution after durability. + */ + insertSessionBefore(sessionId: SessionId, beforeSessionId?: SessionId): Promise + + /** + * Remove a session from this workspace's account. Idempotent: an id not on + * the account resolves without writing (decided on the domain write chain, + * like attach). Never touches the session's own stored log. + * @param sessionId - The session to remove. + * @returns resolution after durability. + */ + detachSession(sessionId: SessionId): Promise + + /** + * Live directory check, uncached: whether {@link path} currently exists and + * is a directory. A missing directory never mutates the record — the + * directory may only be temporarily moved. + * @returns `'ok'` when the directory exists, `'missing-dir'` otherwise. + */ + status(): Promise<'ok' | 'missing-dir'> +} +``` + +所有权的真源是记录中有序的 `sessionIds`,绝不从会话 cwd 派生——但成员资格要求两者同时成立:账本上有其 id,且 header 的规范 cwd 等于工作区路径,因此一个会话在结构上至多属于一个工作区。失败的写入会拒绝(`insertSessionBefore` 的账本错误以 `WorkspaceMoveInvalidError` 拒绝,存储失败以普通错误拒绝);每次被接受的变更都盖上 `updatedAt` 时间戳,并持久修剪不再通过成员资格检查的候选项。 + +## 注册表:`ctx.workspace` + +`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样抛出 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好补完被标记的那次变更,而没有标记的顺序/表不一致则作为损坏大声失败。 + +会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑,而没有 cwd 的历史遗留会话保持 Ungrouped。 + +## 消费方 + +[dsh-host-apiproxy](../../packages/host/apiproxy) 是产品消费方:它经 `ctx.workspace` 向 GUI 客户端提供工作区的 CRUD,并执行上文「先建会话再 attach」的流程。[dsh-workspace-context](../../packages/context/workspace-context) 尽管名字如此,却**不是**消费方:它在 agent 自己的 cwd 下发现 AGENTS.md 风格的指令文件,从不触碰 `ctx.workspace`——两者共用的这个词指的是用户的工作目录,而非本注册表的实体。 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index b710bebe76..b3fdfb7546 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -252,6 +252,7 @@ export const LINK_MAP: Record = { Workspace: 'workspace.md', WorkspaceId: 'workspace.md', WebBootGraph: 'client-modules.md', + TelemetryRecord: 'telemetry.md', } /** TypeScript lib and pinned framework types that have no repository-owned data page. */ @@ -312,7 +313,6 @@ const TYPE_LINK_EXEMPTIONS: Readonly> = { SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts', SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', SubagentRunInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', - TelemetryRecord: 'seam-local record contract is owned by packages/session/session-telemetry/src/index.ts', WorkflowAgentEndInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts', WorkflowAgentInfo: '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', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index b53ff60989..4650150a7d 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -199,7 +199,7 @@ { "doc": "docs/subsystems/goal.md", "symbol": "GoalView", - "source": "packages/goal/goal/src/types.ts" + "source": "packages/goal/goal/src/domain.ts" }, { "doc": "docs/subsystems/goal.md", @@ -219,12 +219,12 @@ { "doc": "docs/subsystems/goal.md", "symbol": "CreateGoalRequest", - "source": "packages/goal/goal/src/types.ts" + "source": "packages/goal/goal/src/domain.ts" }, { "doc": "docs/subsystems/goal.md", "symbol": "EditGoalRequest", - "source": "packages/goal/goal/src/types.ts" + "source": "packages/goal/goal/src/domain.ts" }, { "doc": "docs/subsystems/goal.md", @@ -1475,11 +1475,6 @@ "symbol": "SettingsUpdateSource", "source": "packages/settings/settings/src/index.ts" }, - { - "doc": "docs/subsystems/credentials.md", - "symbol": "CredentialRef", - "source": "packages/credentials/credentials/src/index.ts" - }, { "doc": "docs/subsystems/credentials.md", "symbol": "ResolvedCredential", @@ -1505,69 +1500,6 @@ "symbol": "SettingsPathOp", "source": "packages/settings/settings/src/index.ts" }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTLookupMap", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTContextMap", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTLookupDefinition", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTCodec", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "InvocationParameterDescriptor", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "InvocationDescriptor", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTService", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTRemoteNamespaceMap", - "source": "packages/typert/type-meta/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "InvokeRemoteRequest", - "source": "packages/api/gateway/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypertGatewayErrorCode", - "source": "packages/api/gateway/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypertGateway", - "source": "packages/api/gateway/src/types.ts" - }, - { - "doc": "docs/subsystems/typert.md", - "symbol": "TypeRTClientRemote", - "source": "packages/typert/type-meta/src/types.ts" - "doc": "docs/subsystems/permission.md", - "symbol": "PresetSpec", - "source": "packages/interaction/permission/src/index.ts" - }, { "doc": "docs/subsystems/permission.md", "symbol": "Config", @@ -1687,6 +1619,81 @@ "doc": "docs/subsystems/client-modules.md", "symbol": "WebBootGraph", "source": "packages/client/modules/src/client/manifest.ts" + }, + { + "doc": "docs/subsystems/telemetry.md", + "symbol": "TelemetrySeverity", + "source": "packages/telemetry/session-telemetry/src/index.ts" + }, + { + "doc": "docs/subsystems/telemetry.md", + "symbol": "TelemetryRecord", + "source": "packages/telemetry/session-telemetry/src/index.ts" + }, + { + "doc": "docs/subsystems/telemetry.md", + "symbol": "TelemetryBackend", + "source": "packages/telemetry/session-telemetry/src/index.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTLookupMap", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTContextMap", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTLookupDefinition", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTCodec", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "InvocationParameterDescriptor", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "InvocationDescriptor", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTService", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTRemoteNamespaceMap", + "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "InvokeRemoteRequest", + "source": "packages/api/gateway/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypertGatewayErrorCode", + "source": "packages/api/gateway/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypertGateway", + "source": "packages/api/gateway/src/types.ts" + }, + { + "doc": "docs/subsystems/typert.md", + "symbol": "TypeRTClientRemote", + "source": "packages/typert/type-meta/src/types.ts" } ] } From a8cf78630cbff2d1ea40128dfbfa1ace4e7b8b15 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:52:35 +0800 Subject: [PATCH 10/19] docs: align zh subsystem pages with late en-side precision edits The translators' clause-by-clause verification pass caught the English pages' final wording refinements (tui built-in dialogs enumeration and display()'s line-feed exception, invariants whitespace-padded/companion phrasing, client-modules fiber-lifecycle parenthetical, storage strict sequence, workspace first-successful-start) landing after the initial zh versions; this aligns the eight pages and re-records their pairs. --- docs/subsystems/client-modules.i18n.yaml | 2 +- docs/subsystems/client-modules.zh.md | 6 +++--- docs/subsystems/core.i18n.yaml | 2 +- docs/subsystems/core.zh.md | 2 +- docs/subsystems/http-server.i18n.yaml | 2 +- docs/subsystems/http-server.zh.md | 6 +++--- docs/subsystems/invariants.i18n.yaml | 2 +- docs/subsystems/invariants.zh.md | 10 +++++----- docs/subsystems/permission.i18n.yaml | 2 +- docs/subsystems/permission.zh.md | 6 +++--- docs/subsystems/plan.i18n.yaml | 2 +- docs/subsystems/plan.zh.md | 4 ++-- docs/subsystems/storage.i18n.yaml | 2 +- docs/subsystems/storage.zh.md | 4 ++-- docs/subsystems/workspace.i18n.yaml | 2 +- docs/subsystems/workspace.zh.md | 6 +++--- 16 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index a9a68fd065..9c30e276d4 100644 --- a/docs/subsystems/client-modules.i18n.yaml +++ b/docs/subsystems/client-modules.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c -client-modules.zh.md: bbcdb04cdce8a0d5256d2490deb238812e9a47fc +client-modules.zh.md: e04bd8ac0c6a1566307559f02fc8075c907c0a7f diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index bbcdb04cdc..e04bd8ac0c 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -42,17 +42,17 @@ interface WebBootGraph { } ``` -每一行的 `rev` 是该 bundle 的内容哈希,并作为使缓存失效的查询参数附在 URL 上;图的 `rev` 对组合后的各行做哈希,因此任何一行的变化都会改变它。`immediately` 标记第一阶段预取档位(在模块面启动期间 fetch 并执行,只做工厂登记);惰性行在首次 import 时才拉取。 +每一行的 `rev` 是该 bundle 的内容哈希,并作为使缓存失效的查询参数附在 URL 上;图的 `rev` 对组合后的各行做哈希,因此任何一行的变化都会改变它。`immediately` 标记第一阶段预取档位(在模块面启动期间 fetch 并执行,只做登记);惰性行在首次 import 时才拉取。 ## 扫描 包加入这张表的方式,是在自己的 package.json 中声明 `dshClient`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。 -扫描是单包增量的;不存在全量重扫代码路径。每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。 +扫描是单包增量的;不存在全量重扫代码路径。fiber 构造或 dispose(资源释放)时的每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。 包元数据——包括「非 client 包」这一否定结论——按名缓存且永不过期:插件集合的变更在重启后生效。fiber 重启原样复用其行与 rev;bundle 内容变更只经 `rebuilt()` 到达图。 -## 路由 +## bundle 路由与 index 转换 `GET`/`HEAD /plugins//client.js` 以 `no-cache` 从磁盘提供已注册的 bundle(锚定一致性的是 rev 查询参数,而非 HTTP 缓存);其他方法返回 405。未知 id——或已注册、但 bundle 因尚未构建而不可读的行——回应一个大声的 404,而不是让载体的 SPA 回退把 HTML 当作 JavaScript 发出。index 转换在每次 index 渲染时注入当前图,因此刷新页面总是针对实时组合启动。 diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index d0188cb2f3..8bf98e026d 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md core.md: 1d0bf6c61500817860dfdd452bddc25adbbeffcc -core.zh.md: 06fd3b5a48d5267c9856c20589772047b6105b2c +core.zh.md: 2b4766dcfcf1901de2bb8a8f9168888a67af7fba diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 06fd3b5a48..2b4766dcfc 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -48,7 +48,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数 | [workflow.md](workflow.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | | [permission.md](permission.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 | | [plan.md](plan.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 | -| [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 转换 | | [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | | [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 | diff --git a/docs/subsystems/http-server.i18n.yaml b/docs/subsystems/http-server.i18n.yaml index cb2c734255..8369b94dac 100644 --- a/docs/subsystems/http-server.i18n.yaml +++ b/docs/subsystems/http-server.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/http-server.md http-server.md: 565df72f2720a1211f50650d4b1bc8d6df4a007a -http-server.zh.md: 93b056648f044f6c79b85a6069deb3123f9a7a3e +http-server.zh.md: 69e4a0bd7a1576737583625b23fac2ed05d8ca61 diff --git a/docs/subsystems/http-server.zh.md b/docs/subsystems/http-server.zh.md index fc5e18414b..69e4a0bd7a 100644 --- a/docs/subsystems/http-server.zh.md +++ b/docs/subsystems/http-server.zh.md @@ -24,7 +24,7 @@ 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 表,再取最长匹配前缀,最后落到静态 dist 回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,启动窗口期内尚未被认领的请求全部由回退应答。回退遵循固定语义:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 提供([`static.ts`](../../packages/host/webserver/src/static.ts))。 ## 配置 @@ -44,6 +44,6 @@ interface Config { ## 服务 -`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会从 init 抛出,形成一个 FAILED fiber,由启动阶段的大声失败巡检报告。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层面的契约,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,`config.port` 为 0 时读到的是操作系统分配的值。 +`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会从 init 抛出,形成一个 FAILED fiber,由启动的大声失败 sweep 上报。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层契约,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,`config.port` 为 0 时读到的是操作系统分配的值。 -处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能保持响应打开(如 SSE(Server-Sent Events)),而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。 +处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包(package)从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。 diff --git a/docs/subsystems/invariants.i18n.yaml b/docs/subsystems/invariants.i18n.yaml index dc5a8a6d0d..27b6ac8e87 100644 --- a/docs/subsystems/invariants.i18n.yaml +++ b/docs/subsystems/invariants.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/invariants.md invariants.md: 143a7feabbab18556f595a78c7aef17caa6d3028 -invariants.zh.md: ab7aa0221101400d552ff47e1cfa3275d99a0b6d +invariants.zh.md: fb497857c02202544edf44ccfe3a2935a988e30e diff --git a/docs/subsystems/invariants.zh.md b/docs/subsystems/invariants.zh.md index ab7aa02211..fb497857c0 100644 --- a/docs/subsystems/invariants.zh.md +++ b/docs/subsystems/invariants.zh.md @@ -2,7 +2,7 @@ [English](invariants.md) | 中文 -[dsh-invariants](../../packages/support/invariants) 是面向包(package)自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 伴随插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定;seam 设计由[不变式服务 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md)规定。 +[dsh-invariants](../../packages/support/invariants) 是面向包(package)自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 配套插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定;seam 设计由[不变式服务 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md)规定。 源码:[`packages/support/invariants/src/index.ts`](../../packages/support/invariants/src/index.ts) @@ -20,7 +20,7 @@ interface Config { } ``` -一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时大声失败:空白、带空白填充、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/support/invariants/README.md))。 +一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时大声失败:空白、首尾带空白、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/support/invariants/README.md))。 ## 安装器 @@ -52,8 +52,8 @@ interface InvariantInstaller { ## 服务 -`ctx.invariants.register(packageName, installer)` 为完整 npm 包名保留唯一一个活跃注册,并返回其绑定到 effect 的 disposer。即使过滤器使安装器保持不活跃,保留依然成立,因此两个插件绝不可能静默认领同一个包名;重复、空白或含空白字符的名称会抛出异常。安装器失败会原子地 dispose(资源释放)子 fiber 并释放保留。服务拥有每个注册 fiber,而返回的 disposer 同时属于伴随插件的 fiber:卸载任一侧都会移除监听器、trace 状态和保留项,因此伴随插件可以重载并再次注册同一名称,不留残余状态。 +`ctx.invariants.register(packageName, installer)` 为完整 npm 包名保留唯一一个活跃注册,并返回其绑定到 effect 的 disposer。即使过滤器使安装器保持不活跃,保留依然成立,因此两个插件绝不可能静默地认领同一个包名;重复、空白或含空白字符的名称会抛出异常。安装器失败会原子地 dispose(资源释放)子 fiber 并释放保留。服务拥有每个注册 fiber,而返回的 disposer 同时属于配套插件的 fiber:卸载任一侧都会移除监听器、trace 状态和保留项,因此配套插件可以重载并再次注册同一名称,不留残余状态。 -## 伴随插件契约 +## 配套插件契约 -每个工作区包都拥有一个 `./invariant` 伴随插件([包契约](../../packages/AGENTS.md));发布与注册是穷尽式的,但断言刻意不为凑数而虚构。只有当包对某个可观察事件或可变数据拥有所有权关系时,伴随插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行伴随插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。 +每个工作区包都拥有一个 `./invariant` 配套插件([包契约](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包对某个可观察事件或可变数据拥有所有权关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。 diff --git a/docs/subsystems/permission.i18n.yaml b/docs/subsystems/permission.i18n.yaml index f800d4ff08..284f29bb62 100644 --- a/docs/subsystems/permission.i18n.yaml +++ b/docs/subsystems/permission.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/permission.md permission.md: 49dd51ff86d0dbce26fca0ed21527f71bb144dec -permission.zh.md: 5fb93e9f923582e6c530bac4a22e2ce62cff156d +permission.zh.md: c2ab6ecdfb4b80511107bee41e080b06fae45f2c diff --git a/docs/subsystems/permission.zh.md b/docs/subsystems/permission.zh.md index 5fb93e9f92..c2ab6ecdfb 100644 --- a/docs/subsystems/permission.zh.md +++ b/docs/subsystems/permission.zh.md @@ -2,13 +2,13 @@ [English](permission.md) | 中文 -[dsh-permission](../../packages/ui/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮,即[沙箱模式](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/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)负责决策依据。 源码:[`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts) ## 预设表 -预设是一个表键,映射到一组沙箱/审批组合,外加可选的客户端展示信息;默认预设表自带 `workspace-write`(`workspace-write` + `ask`)和 `danger-full-access`(`danger-full-access` + `never`)。 +预设是一个表键,映射到一个沙箱/审批组合,外加可选的客户端展示信息;默认预设表自带 `workspace-write`(`workspace-write` + `ask`)和 `danger-full-access`(`danger-full-access` + `never`)。 ```ts type-equiv /** One preset's sandbox/approval bundle and optional client presentation. */ @@ -36,7 +36,7 @@ interface Config { } ``` -该服务要求一个施加沙箱隔离的 `ctx.bash` 执行器和 `ctx.approval`,配置错误在插件加载时即失败:名为 `custom` 的表项会抛出异常(该名称保留给派生的「非预设」状态);在不施加隔离的 bash 执行器(没有 `sandboxMode` 能力事实)之上组合同样抛出异常,因为预设捆绑了一个沙箱模式。 +该服务要求一个施加隔离的 `ctx.bash` 执行器和 `ctx.approval`,配置错误在插件加载时即失败:名为 `custom` 的表项会抛出异常(该名称保留给派生的「非预设」状态);在不施加隔离的 bash 执行器(没有 `sandboxMode` 能力事实)之上组合同样抛出异常,因为预设捆绑了一个沙箱模式。 ## 当前预设与派生的 `custom` diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index 3ae156615f..c73912c178 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md plan.md: f67e7734ca3fd55c284d3d41227f8241de7b9f5f -plan.zh.md: 565cf2016636acb6c26d20c731b9e6e374cee1da +plan.zh.md: 27114c3c1c2acae3868b9c5bff37485b6da85377 diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index af21a6d027..27114c3c1c 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -14,7 +14,7 @@ 由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到轮次边界。`set(agent, active)` 记录待定选择(目标值与已记录或已在待定中的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`,即影响当前步骤的已记录状态,加上正在等待边界的乐观选择。 -服务会在三类边界处、于受影响的请求组装之前冲刷一条待定选择:提示词提交、普通轮次延续和请求恢复重试。冲刷在下游监听器链之后运行,因此在某个异步监听器等待期间到达的选择,仍会影响该边界紧接其后的那个请求。冲刷失败会被隔离(计划策略绝不能阻塞提示词或轮次),追加失败的选择保持待定,等待后续边界。已冲刷的用户选择还会以一条插件来源的 `user/message` 通知叙述这次切换,但仅当最后记录的请求头描述的是另一种状态时才叙述,因此模型恰好在上下文变化时被告知,且绝不重复。空闲时做出的待定选择只存在于进程内,进程在下一个边界之前退出即丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。 +服务会在三类边界处、于受影响的请求组装之前冲刷一条待定选择:提示词提交、普通轮次延续和请求恢复重试。冲刷在下游监听器链之后运行,因此在某个异步监听器等待期间到达的选择,仍会影响紧随该边界的那个请求。冲刷失败会被收容(计划策略绝不能阻塞提示词或轮次),追加失败的选择保持待定,等待后续边界。已冲刷的用户选择还会以一条插件来源的 `user/message` 通知叙述这次切换,但仅当最后记录的请求头描述的是另一种状态时才叙述,因此模型恰好在上下文变化时被告知,且绝不重复。空闲时做出的待定选择只存在于进程内,进程在下一个边界之前退出即丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。 ## 配置 @@ -26,7 +26,7 @@ interface PlanModeConfig { } ``` -`section` 缺失、为空白或不是字符串,以及任何未知键,都会在插件加载时失败,而不是静默地什么都不影响。计划模式激活期间,确切的 `section` 文本以 order 50 渲染为 `plan:policy` [系统提示词段落](system-prompt.md);未激活的计划模式不贡献任何文本。 +`section` 缺失、为空白或不是字符串,以及任何未知键,都会在插件加载时失败,而不是静默地不产生任何指引。计划模式激活期间,确切的 `section` 文本以 order 50 渲染为 `plan:policy` [系统提示词段落](system-prompt.md);未激活的计划模式不贡献任何文本。 ## 退出工具与 `/plan` 命令 diff --git a/docs/subsystems/storage.i18n.yaml b/docs/subsystems/storage.i18n.yaml index 92387332f0..6560eed831 100644 --- a/docs/subsystems/storage.i18n.yaml +++ b/docs/subsystems/storage.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/storage.md storage.md: 5d54d7337b5ef52e24179758130b9fa8cd196376 -storage.zh.md: c575f989fe4b3536ce3c8d3b3af08ebda418166d +storage.zh.md: 46346b64351322d40de166a71f7a3204c8902b2d diff --git a/docs/subsystems/storage.zh.md b/docs/subsystems/storage.zh.md index c575f989fe..46346b6435 100644 --- a/docs/subsystems/storage.zh.md +++ b/docs/subsystems/storage.zh.md @@ -64,7 +64,7 @@ interface DomainSpec { } ``` -`defineDomain(spec)` 固定 spec 的字面量类型,并在拥有方的模块加载时、任何介质被触碰之前就大声失败:领域名或表名不匹配 `UNIT_NAME_RE`、版本不是非负整数、global schema 接受 `null`,这些都会抛出(`null` 是介质的「从未写入」哨兵值,可空的 global 一旦存储就无法往返还原)。`domainTable(schema)` 声明一张表,其键类型是编译期的幻影类型(通常是[品牌化 id](core.md#branded-ids));`descriptorOf(spec)` 投影出面向后端的 unit 描述符。 +`defineDomain(spec)` 固定 spec 的字面量类型,并在拥有方的模块加载时、任何介质被触碰之前就大声失败:领域名或表名不匹配 `UNIT_NAME_RE`、版本不是非负整数、global schema 接受 `null`,这些都会抛出(`null` 是介质的「从未写入」哨兵值,可空的 global 一旦存储就无法往返还原)。`domainTable(schema)` 声明一张表,其键类型是仅存在于编译期的 phantom 类型(通常是[品牌化 id](core.md#branded-ids));`descriptorOf(spec)` 投影出面向后端的 unit 描述符。 ## 打开的领域 @@ -99,7 +99,7 @@ interface Domain { ## 领域 facility:`ctx.storageDomain` -`DomainFacility`([签名](../cordis-catalog/services.md#ctxstoragedomain--domainfacility))在经过路由的后端之上打开已声明的领域。路由是领域插件的配置,绝不属于枢纽:`backend` 指定必填的默认路由,`routes` 按领域名逐个覆盖。`open(spec)` 逐步执行,任一步失败都使整个调用失败:拒绝已打开或仍在关闭中的名称(`already-open`),解析路由(`backend-not-found`),要求后端具备 `kv` facet(`facet-unsupported`),打开 unit(后端的 `version-mismatch`/`malformed-medium` 原样透传),并按 spec 的 zod schema 校验每条已存储记录和 global(`invalid-record`,附带出错的表与键)。调用方拥有返回的句柄,并用 `Domain.close()` 释放它;插件卸载时仍处于打开状态的领域由 facility 负责关闭,已关闭领域的名称只有在拆除完全结束后才释放出来供重新打开。`get(name)` 是无类型的诊断查找;`closeAll()` 是卸载路径。 +`DomainFacility`([签名](../cordis-catalog/services.md#ctxstoragedomain--domainfacility))在经过路由的后端之上打开已声明的领域。路由是领域插件的配置,绝不属于枢纽:`backend` 指定必填的默认路由,`routes` 按领域名逐个覆盖。`open(spec)` 按严格顺序执行,每一步失败都使整个调用失败:拒绝已打开或仍在关闭中的名称(`already-open`),解析路由(`backend-not-found`),要求后端具备 `kv` facet(`facet-unsupported`),打开 unit(后端的 `version-mismatch`/`malformed-medium` 原样透传),并按 spec 的 zod schema 校验每条已存储记录和 global(`invalid-record`,附带出错的表与键)。调用方拥有返回的句柄,并用 `Domain.close()` 释放它;插件卸载时仍处于打开状态的领域由 facility 负责关闭,已关闭领域的名称只有在拆除完全结束后才释放出来供重新打开。`get(name)` 是无类型的诊断查找,命中的是每个类型化句柄背后包内私有的 `DomainImpl` 运行时;`closeAll()` 是卸载路径。 ## 变更事件:`domain/changed` diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml index 5540d00aab..16ad3b95a3 100644 --- a/docs/subsystems/workspace.i18n.yaml +++ b/docs/subsystems/workspace.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/workspace.md workspace.md: 2726115457905687e3c2cfc35a7a3f7251e6347c -workspace.zh.md: 190c44b2d73f9e123242c34b5429839abe3b9297 +workspace.zh.md: 3a752617a58e4c9b62ec4084d2dc518015030952 diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md index 190c44b2d7..3a752617a5 100644 --- a/docs/subsystems/workspace.zh.md +++ b/docs/subsystems/workspace.zh.md @@ -2,7 +2,7 @@ [English](workspace.md) | 中文 -工作区(workspace)是用户工作目录的持久记录:一个建立在规范路径之上的稳定 id、一个显示标题,以及归属于它的会话的有序账本。该子系统是单个包(package)([dsh-workspace](../../packages/workspace/workspace),`ctx.workspace`)——一项宿主侧可选能力,不属于 agent loop(智能体循环)主干,并且对模型不可见(没有工具、没有提示词文本、没有会话事件)。它通过[存储领域数据形式](storage.md)存储自己的记录,并对照 [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log) 校验会话成员资格,因此 `storageDomain` 与 `sessionPersistence` 是必需的启动依赖:持久化这一侧不可用时插件保持 pending,而不会被误认为历史为空。设计记录:[领域 KV 存储 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md);引导与 GUI 顺序:[Workspace UI 产品流程 Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md)。 +工作区(workspace)是用户工作目录的持久记录:一个建立在规范路径之上的稳定 id、一个显示标题,以及归属于它的会话的有序账本。该子系统是单个包(package)([dsh-workspace](../../packages/workspace/workspace),`ctx.workspace`)——一项宿主侧可选能力,不属于 agent loop(智能体循环)主干,并且对模型不可见(没有工具、没有提示词文本、没有会话事件)。它通过[存储领域数据形式](storage.md)存储自己的记录,并对照 [`SessionHeader.cwd`](persistence.md#sessionheader--metadata-beside-the-log) 校验会话成员资格,因此 `storageDomain` 与 `sessionPersistence` 是必需的启动依赖:持久化这一依赖不可用时,插件保持 pending,而不是把这种不可用误当作空历史。设计记录:[领域 KV 存储 Agent Note(agent 决策记录)](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md);引导与 GUI 顺序:[Workspace UI 产品流程 Agent Note](../../.agents/notes/implemented/feature/2026-07-25-workspace-ui-product-flow.md)。 源码:[`packages/workspace/workspace/src/types.ts`](../../packages/workspace/workspace/src/types.ts) @@ -112,9 +112,9 @@ interface Workspace { ## 注册表:`ctx.workspace` -`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样抛出 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好补完被标记的那次变更,而没有标记的顺序/表不一致则作为损坏大声失败。 +`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好补完被标记的那次变更,而没有标记的顺序/表不一致则作为损坏大声失败。 -会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑,而没有 cwd 的历史遗留会话保持 Ungrouped。 +会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。 ## 消费方 From 061100f6f2cc1608a2c73811d74382f9d92b27d7 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:20:38 +0800 Subject: [PATCH 11/19] docs: fix four factual errors found by Codex review round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - plan.md/.zh.md (+ plan-mode module JSDoc and README pair): the sole flush point is the prepended agent/step listener; prompt admission is pre-turn and never flushes — the pages had it backwards. - workspace.md/.zh.md: pending-mutation recovery deletes the marked table row — completing an interrupted delete but rolling back an interrupted create; 'completes exactly the marked mutation' oversold. - telemetry.md/.zh.md: delivery is best-effort with possible loss AND duplication, not at-most-once; TelemetryRecord.attributes JSDoc (source + both fences) now lists session.seed_length, which the coordinator emits for forked sessions. - Agent Note consequence bullet: nine pages cover ten services (storage owns two keys); client-modules.zh.md wire-single-source phrasing. Pairs re-recorded; plan-mode and telemetry suites green. --- .../2026-06-20-core-data-structures-catalog.i18n.yaml | 4 ++-- .../process/2026-06-20-core-data-structures-catalog.md | 2 +- .../2026-06-20-core-data-structures-catalog.zh.md | 2 +- docs/subsystems/client-modules.i18n.yaml | 2 +- docs/subsystems/client-modules.zh.md | 2 +- docs/subsystems/plan.i18n.yaml | 4 ++-- docs/subsystems/plan.md | 2 +- docs/subsystems/plan.zh.md | 2 +- docs/subsystems/telemetry.i18n.yaml | 4 ++-- docs/subsystems/telemetry.md | 10 +++++----- docs/subsystems/telemetry.zh.md | 10 +++++----- docs/subsystems/workspace.i18n.yaml | 4 ++-- docs/subsystems/workspace.md | 2 +- docs/subsystems/workspace.zh.md | 2 +- packages/session/session-telemetry/src/index.ts | 8 ++++---- 15 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml index 5bc51a4e7d..0883a5e9ab 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md -2026-06-20-core-data-structures-catalog.md: 718045bf7ce511a0b766e69e64ef7657eb221189 -2026-06-20-core-data-structures-catalog.zh.md: 1e8473a373c212fa08b171719edd5803e5aa3b05 +2026-06-20-core-data-structures-catalog.md: a8de47a462da13f316236fb7da3f4bfb57edae21 +2026-06-20-core-data-structures-catalog.zh.md: d84c2befaee4c2032b358ed1a7949ac0151ae74c diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index 718045bf7c..a8de47a462 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -58,4 +58,4 @@ The spine-vs-seam rule was tested against `BashExecRequest`, tool schemas and de - The spine-vs-seam line is a reusable scoping tool, not a one-off: the same "the thing you write/hold/receive is core; the machinery that types/renders/persists it is a detail" rule is what later scoped the events/services catalog's harness-vs-inherited tiering. - The `ts type-equiv` fence is a third doc-block category alongside ` ```ts ` (compiled) and ` ```ts ignore-check ` (sketch). A later sibling added a fourth, ` ```ts cordis-catalog ` (generated signature), reusing the same skip-and-exclude treatment. - Adding or reshaping a core type now carries a documentation obligation the author must honor (the gate cannot detect a missing *new* type), backstopped by the `dsh-code-review` checklist. -- Since 2026-07-27 the sub-page tier spans every service-bearing subsystem: the nine `ctx` services without a page (permission presets, plan mode, runtime invariants, the HTTP carrier, storage, TUI extensions, workspaces, client modules, telemetry) gained lean pages, so each harness service and event scope has exactly one owning subsystems page — the precondition for generating per-subsystem service/event reference into these pages instead of flat catalogs. +- Since 2026-07-27 the sub-page tier spans every service-bearing subsystem: nine lean pages (permission presets, plan mode, runtime invariants, the HTTP carrier, storage — owning both `ctx.storage` and `ctx.storageDomain` — TUI extensions, workspaces, client modules, telemetry) cover the ten `ctx` services that had none, so each harness service and event scope has exactly one owning subsystems page — the precondition for generating per-subsystem service/event reference into these pages instead of flat catalogs. diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index 1e8473a373..d84c2befae 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -58,4 +58,4 @@ Status: implemented - 主干与 seam 分界线是一个可复用的范围界定工具,而非一次性的:同一条「你编写/持有/接收的东西是核心;为其提供类型推导/渲染/持久化的机制是细节」规则,后来也被用于界定事件/服务目录的 harness 层与继承层分层。 - `ts type-equiv` 围栏是继 ` ```ts `(编译)和 ` ```ts ignore-check `(草稿)之后的第三种文档块类别。后续的姊妹门禁又增加了第四种 ` ```ts cordis-catalog `(生成签名),复用了相同的跳过并排除处理。 - 添加或重塑核心类型现在附带一项文档义务,作者必须履行(门禁无法检测缺失的*新*类型),由 `dsh-code-review` 检查清单兜底。 -- 自 2026-07-27 起,子页面层级覆盖每个承载服务的子系统:原先没有页面的九个 `ctx` 服务(权限预设、计划模式、运行时不变式、HTTP 载体、存储、终端扩展、工作区、客户端模块、遥测)都获得了精简页面,于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面(而非平铺目录)的前提。 +- 自 2026-07-27 起,子页面层级覆盖每个承载服务的子系统:九个精简页面(权限预设、计划模式、运行时不变式、HTTP 载体、存储——同时拥有 `ctx.storage` 与 `ctx.storageDomain`——终端扩展、工作区、客户端模块、遥测)覆盖了原先没有页面的十个 `ctx` 服务,于是每个 harness 服务和事件作用域都有恰好一个所属的 subsystems 页面——这是把按子系统生成的服务/事件参考写入这些页面(而非平铺目录)的前提。 diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index 9c30e276d4..8988609cdf 100644 --- a/docs/subsystems/client-modules.i18n.yaml +++ b/docs/subsystems/client-modules.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c -client-modules.zh.md: e04bd8ac0c6a1566307559f02fc8075c907c0a7f +client-modules.zh.md: 5da7740e4c9272a3516f1a28712be7c95f29a6b3 diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index e04bd8ac0c..5da7740e4c 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -8,7 +8,7 @@ Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client ## wire -图是 Node 半与浏览器半之间的线上单源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。 +图是 Node 半与浏览器半之间协议层的唯一事实来源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。 ```ts type-equiv /** diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index c73912c178..7c20a42864 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md -plan.md: f67e7734ca3fd55c284d3d41227f8241de7b9f5f -plan.zh.md: 27114c3c1c2acae3868b9c5bff37485b6da85377 +plan.md: eb88047573536cd15be587b1419271bfbd93e9f1 +plan.zh.md: 5a838a045d63f2cabad80cad8af12df5da7e87f3 diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index f67e7734ca..eb88047573 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -14,7 +14,7 @@ Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/s Because every session event is turn-enclosed, a user selection is held as pending intent until a turn boundary. `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 service flushes one pending selection before the affected request assembly at three boundaries: prompt submission, ordinary turn continuation, and request-recovery retry. The flush runs after the downstream listener chain, so a selection arriving while an async listener awaits still shapes the request that boundary precedes. A flush failure is contained — plan policy can never block a prompt or 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)). ## Configuration diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index 27114c3c1c..5a838a045d 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -14,7 +14,7 @@ 由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到轮次边界。`set(agent, active)` 记录待定选择(目标值与已记录或已在待定中的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`,即影响当前步骤的已记录状态,加上正在等待边界的乐观选择。 -服务会在三类边界处、于受影响的请求组装之前冲刷一条待定选择:提示词提交、普通轮次延续和请求恢复重试。冲刷在下游监听器链之后运行,因此在某个异步监听器等待期间到达的选择,仍会影响紧随该边界的那个请求。冲刷失败会被收容(计划策略绝不能阻塞提示词或轮次),追加失败的选择保持待定,等待后续边界。已冲刷的用户选择还会以一条插件来源的 `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))。 ## 配置 diff --git a/docs/subsystems/telemetry.i18n.yaml b/docs/subsystems/telemetry.i18n.yaml index 9f6fdb3af3..d7544c7ddf 100644 --- a/docs/subsystems/telemetry.i18n.yaml +++ b/docs/subsystems/telemetry.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md -telemetry.md: 4942a402dacb40ed64070c2b7824bfea7a14c872 -telemetry.zh.md: c99b287fd4cb4e9bc585ffe365ca08df37f6ccd1 +telemetry.md: cf6d3556a638375d939b29cc28941bf9c9ec7ea4 +telemetry.zh.md: 95eadb26581a6a96ce208d7c3b265d89455de4ba diff --git a/docs/subsystems/telemetry.md b/docs/subsystems/telemetry.md index 4942a402da..cf6d3556a6 100644 --- a/docs/subsystems/telemetry.md +++ b/docs/subsystems/telemetry.md @@ -38,10 +38,10 @@ interface TelemetryRecord { /** * Identity attributes, deliberately minimal: ledger records carry * `session.id`, `event.type`, `event.seq`, plus `session.cwd` / - * `session.parent_id` when the header has them; ops records carry - * `telemetry.op`, `session.id`, and (for `agent-error`) `agent.id`, - * `turn`, `step`, `error.name`. Anything recoverable from the body is - * intentionally NOT duplicated here. + * `session.parent_id` / `session.seed_length` when the header has them; + * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`) + * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the + * body is intentionally NOT duplicated here. */ attributes: Record /** @@ -54,7 +54,7 @@ interface TelemetryRecord { } ``` -Only the first `assistant/chunk` of each `(turn, step)` ships — the stream-started signal; the rest drop at capture, so `seq` gaps are routine on the wire and never a loss signal. Every other [session event](session.md) type, including plugin-merged ones the seam never heard of, passes through whole. Delivery is at-most-once downstream of the handoff; receivers dedupe on `(session.id, event.seq)`. +Only the first `assistant/chunk` of each `(turn, step)` ships — the stream-started signal; the rest drop at capture, so `seq` gaps are routine on the wire and never a loss signal. Every other [session event](session.md) type, including plugin-merged ones the seam never heard of, passes through whole. Delivery is best-effort: the cursor marks handed-off, not delivered, records can be lost (crash, reload window) and duplicated (cursor-less re-adoption, SDK retries), so receivers dedupe on `(session.id, event.seq)`. ## The backend contract diff --git a/docs/subsystems/telemetry.zh.md b/docs/subsystems/telemetry.zh.md index c99b287fd4..95eadb2658 100644 --- a/docs/subsystems/telemetry.zh.md +++ b/docs/subsystems/telemetry.zh.md @@ -38,10 +38,10 @@ interface TelemetryRecord { /** * Identity attributes, deliberately minimal: ledger records carry * `session.id`, `event.type`, `event.seq`, plus `session.cwd` / - * `session.parent_id` when the header has them; ops records carry - * `telemetry.op`, `session.id`, and (for `agent-error`) `agent.id`, - * `turn`, `step`, `error.name`. Anything recoverable from the body is - * intentionally NOT duplicated here. + * `session.parent_id` / `session.seed_length` when the header has them; + * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`) + * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the + * body is intentionally NOT duplicated here. */ attributes: Record /** @@ -54,7 +54,7 @@ interface TelemetryRecord { } ``` -每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。交接下游的投递为至多一次(at-most-once);接收端基于 `(session.id, event.seq)` 去重。 +每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端基于 `(session.id, event.seq)` 去重。 ## 后端契约 diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml index 16ad3b95a3..a50df35f91 100644 --- a/docs/subsystems/workspace.i18n.yaml +++ b/docs/subsystems/workspace.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/workspace.md -workspace.md: 2726115457905687e3c2cfc35a7a3f7251e6347c -workspace.zh.md: 3a752617a58e4c9b62ec4084d2dc518015030952 +workspace.md: bade3526964055d5bf4448265c5898f1e66a1fce +workspace.zh.md: b0437e42b644c1ce11d9a667972f7b236cf4caa1 diff --git a/docs/subsystems/workspace.md b/docs/subsystems/workspace.md index 2726115457..bade352696 100644 --- a/docs/subsystems/workspace.md +++ b/docs/subsystems/workspace.md @@ -112,7 +112,7 @@ Ownership truth is the record's ordered `sessionIds`, never derived from session ## The registry: `ctx.workspace` -`WorkspaceRegistry` ([signatures](../cordis-catalog/services.md#ctxworkspace--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup completes exactly the marked mutation, and an unmarked order/table mismatch fails loud as corruption. +`WorkspaceRegistry` ([signatures](../cordis-catalog/services.md#ctxworkspace--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption. Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`. diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md index 3a752617a5..b0437e42b6 100644 --- a/docs/subsystems/workspace.zh.md +++ b/docs/subsystems/workspace.zh.md @@ -112,7 +112,7 @@ interface Workspace { ## 注册表:`ctx.workspace` -`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好补完被标记的那次变更,而没有标记的顺序/表不一致则作为损坏大声失败。 +`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。 会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。 diff --git a/packages/session/session-telemetry/src/index.ts b/packages/session/session-telemetry/src/index.ts index 0198df3140..6a8e9ed2ad 100644 --- a/packages/session/session-telemetry/src/index.ts +++ b/packages/session/session-telemetry/src/index.ts @@ -71,10 +71,10 @@ export interface TelemetryRecord { /** * Identity attributes, deliberately minimal: ledger records carry * `session.id`, `event.type`, `event.seq`, plus `session.cwd` / - * `session.parent_id` when the header has them; ops records carry - * `telemetry.op`, `session.id`, and (for `agent-error`) `agent.id`, - * `turn`, `step`, `error.name`. Anything recoverable from the body is - * intentionally NOT duplicated here. + * `session.parent_id` / `session.seed_length` when the header has them; + * ops records carry `telemetry.op`, `session.id`, and (for `agent-error`) + * `agent.id`, `turn`, `step`, `error.name`. Anything recoverable from the + * body is intentionally NOT duplicated here. */ attributes: Record /** From 0dd3ac14eb177fe1fb3802497846b847686c4249 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:36:30 +0800 Subject: [PATCH 12/19] docs: regenerate catalogs after the plan-mode JSDoc expansion shifted source lines Codex round-2: the two-line module-JSDoc fix moved every later declaration in packages/plan/plan-mode/src/index.ts, so the three generated catalogs' source-line links went stale. Three one-line regenerated updates; doc-sync back to 25/25. --- scripts/type-equiv.manifest.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 4650150a7d..e1f80b96b6 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -1694,6 +1694,16 @@ "doc": "docs/subsystems/typert.md", "symbol": "TypeRTClientRemote", "source": "packages/typert/type-meta/src/types.ts" + }, + { + "doc": "docs/subsystems/credentials.md", + "symbol": "CredentialRef", + "source": "packages/credentials/credentials/src/index.ts" + }, + { + "doc": "docs/subsystems/permission.md", + "symbol": "PresetSpec", + "source": "packages/interaction/permission/src/index.ts" } ] } From bd62b6c0322c5eaa5ce10d54351f211352fb22f5 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:20:22 +0800 Subject: [PATCH 13/19] docs: qualify the telemetry dedupe key as ledger-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ds-review-bot: ops records deliberately omit event.seq, so the (session.id, event.seq) dedupe advice cannot apply to them — they are alert signals that tolerate duplicates. Both language sides; pair re-recorded. --- docs/subsystems/telemetry.i18n.yaml | 4 ++-- docs/subsystems/telemetry.md | 2 +- docs/subsystems/telemetry.zh.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/subsystems/telemetry.i18n.yaml b/docs/subsystems/telemetry.i18n.yaml index d7544c7ddf..e97a6f2e27 100644 --- a/docs/subsystems/telemetry.i18n.yaml +++ b/docs/subsystems/telemetry.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md -telemetry.md: cf6d3556a638375d939b29cc28941bf9c9ec7ea4 -telemetry.zh.md: 95eadb26581a6a96ce208d7c3b265d89455de4ba +telemetry.md: 92235a26fe278dfd6ac2b2ea5703b5c0d837b336 +telemetry.zh.md: e076abd90bd5054ddbfd6fbe7b725ee45d50a038 diff --git a/docs/subsystems/telemetry.md b/docs/subsystems/telemetry.md index cf6d3556a6..92235a26fe 100644 --- a/docs/subsystems/telemetry.md +++ b/docs/subsystems/telemetry.md @@ -54,7 +54,7 @@ interface TelemetryRecord { } ``` -Only the first `assistant/chunk` of each `(turn, step)` ships — the stream-started signal; the rest drop at capture, so `seq` gaps are routine on the wire and never a loss signal. Every other [session event](session.md) type, including plugin-merged ones the seam never heard of, passes through whole. Delivery is best-effort: the cursor marks handed-off, not delivered, records can be lost (crash, reload window) and duplicated (cursor-less re-adoption, SDK retries), so receivers dedupe on `(session.id, event.seq)`. +Only the first `assistant/chunk` of each `(turn, step)` ships — the stream-started signal; the rest drop at capture, so `seq` gaps are routine on the wire and never a loss signal. Every other [session event](session.md) type, including plugin-merged ones the seam never heard of, passes through whole. Delivery is best-effort: the cursor marks handed-off, not delivered, records can be lost (crash, reload window) and duplicated (cursor-less re-adoption, SDK retries), so receivers dedupe ledger records on `(session.id, event.seq)`; ops records deliberately omit that identity — they are signals to alert on, not entries to sum, and tolerate duplicates instead. ## The backend contract diff --git a/docs/subsystems/telemetry.zh.md b/docs/subsystems/telemetry.zh.md index 95eadb2658..e076abd90b 100644 --- a/docs/subsystems/telemetry.zh.md +++ b/docs/subsystems/telemetry.zh.md @@ -54,7 +54,7 @@ interface TelemetryRecord { } ``` -每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端基于 `(session.id, event.seq)` 去重。 +每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端对 ledger 记录基于 `(session.id, event.seq)` 去重;ops 记录刻意省略这类标识——它们是用于告警的信号,而非用于累加的条目,重复被容忍而非被去重。 ## 后端契约 From 3e1c7351bef4202a65c5f739254dce8a68212695 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:04:21 +0800 Subject: [PATCH 14/19] docs: state the plan-mode flush as a step boundary ds-review-bot round 2: set() during an open multi-step turn applies at the next request derivation in that same turn, so 'next turn boundary' misstated the public timing contract (and propagated into the generated catalogs). set()'s JSDoc, the module JSDoc, the pending-intent field comment, and the subsystem page (both languages) now say step boundary; catalogs regenerated; plan pair re-recorded. --- docs/subsystems/plan.i18n.yaml | 4 ++-- docs/subsystems/plan.md | 4 ++-- docs/subsystems/plan.zh.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index 7c20a42864..3295167755 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md -plan.md: eb88047573536cd15be587b1419271bfbd93e9f1 -plan.zh.md: 5a838a045d63f2cabad80cad8af12df5da7e87f3 +plan.md: c8bb6c9b6a0bcfd20ed6eadece4552d49f3c757c +plan.zh.md: c8acedaef0724581227b57afbf81418a7e538aed diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index eb88047573..c8bb6c9b6a 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -10,9 +10,9 @@ Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/s `plan/mode` (`{ active: boolean }`) is a log-only, whole-value-replace [session event](session.md): durable and replayable, never in the model transcript. `foldPlanMode(events, end?)` returns the last logged value in the prefix, or `false` when there is none — the state in force is always a pure fold of the session log, so resume, fork, and compaction recover it with no live mirror, and UIs observe committed flips through `session/event`. The complete event declaration is in the [persistence log event catalog](../persistence-catalog.md). -## Pending intent and the turn-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 a turn boundary. `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 — 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. 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)). diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index 5a838a045d..c8acedaef0 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -10,9 +10,9 @@ `plan/mode`(`{ active: boolean }`)是仅记日志、整值替换的[会话事件](session.md):持久且可回放,绝不进入模型 transcript(文本记录)。`foldPlanMode(events, end?)` 返回前缀中最后一条已记录值,没有时返回 `false`:生效状态始终是会话日志的纯折叠,因此恢复、fork 与压缩(compaction)无需实时镜像即可将其复原,UI 通过 `session/event` 观察已提交的切换。完整事件声明见[持久化日志事件目录](../persistence-catalog.md)。 -## 待定意图与轮次边界冲刷 +## 待定意图与步骤边界冲刷 -由于每个会话事件都位于轮次之内,用户的选择会作为待定意图保留到轮次边界。`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))。 From 44b035c711482016e91438a163234ecdb8efbfe6 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:11:18 +0800 Subject: [PATCH 15/19] docs: qualify plan-mode timing and workspace no-write claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- docs/AGENTS.md | 4 +- docs/cordis-catalog/events.md | 5 +- docs/cordis-catalog/services.md | 16 +- docs/subsystems/client-modules.i18n.yaml | 4 +- docs/subsystems/client-modules.md | 2 +- docs/subsystems/client-modules.zh.md | 2 +- docs/subsystems/core.i18n.yaml | 4 +- docs/subsystems/core.md | 1 - docs/subsystems/core.zh.md | 1 - docs/subsystems/http-server.i18n.yaml | 4 +- docs/subsystems/http-server.md | 10 +- docs/subsystems/http-server.zh.md | 8 +- docs/subsystems/permission.i18n.yaml | 4 +- docs/subsystems/permission.md | 15 +- docs/subsystems/permission.zh.md | 15 +- docs/subsystems/plan.i18n.yaml | 4 +- docs/subsystems/plan.md | 2 +- docs/subsystems/plan.zh.md | 2 +- docs/subsystems/telemetry.i18n.yaml | 4 +- docs/subsystems/telemetry.md | 15 +- docs/subsystems/telemetry.zh.md | 15 +- docs/subsystems/workspace.i18n.yaml | 4 +- docs/subsystems/workspace.md | 13 +- docs/subsystems/workspace.zh.md | 13 +- .../tests/cordis-catalog-contract.spec.ts | 4 +- packages/workspace/workspace/README.i18n.yaml | 2 +- packages/workspace/workspace/README.zh.md | 1 + packages/workspace/workspace/src/types.ts | 13 +- scripts/gen-cordis-catalog.ts | 492 +++--------------- scripts/type-equiv.manifest.json | 39 +- 30 files changed, 187 insertions(+), 531 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 3bbd4f330d..488a7a1dd6 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -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 | | 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) | | [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 | — | @@ -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)). - **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)). -- **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)). - **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". diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md index 6d060cf26b..fb05eaa8e6 100644 --- a/docs/cordis-catalog/events.md +++ b/docs/cordis-catalog/events.md @@ -824,6 +824,8 @@ Transform one outbound record before it reaches the backend. This waterfall is t '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) ## `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/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/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:22`](../../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)) +- `hmr/reload` — Plugins are being reloaded after a change. ([`vendor/hmr/src/index.ts:21`](../../vendor/hmr/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/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts)) diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index dbab056105..88f1762cff 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -377,6 +377,8 @@ onRebuilt(listener: (id: string, rev: string) => 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) ## `ctx.codeRuntime` — `CodeRuntime` (abstract seam) @@ -866,6 +868,8 @@ tapIndex(transform: (html: string) => string): () => void applyIndexTaps(html: string): string ``` +Types: [WebRoute](../subsystems/http-server.md) + Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts) ## `ctx.invariants` — `InvariantService` @@ -884,6 +888,8 @@ Package-owned invariant registry with global and regex-based selection. 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) ## `ctx.llm` — `LlmService` @@ -1060,7 +1066,7 @@ optionOf(name: string): PresetOption 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) @@ -2036,6 +2042,8 @@ mount(form: K, facility: StorageForms[K]): () => v form(form: K): StorageForms[K] ``` +Types: [StorageForms](../subsystems/storage.md) + Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts) ## `ctx.storageDomain` — `DomainFacility` @@ -2079,6 +2087,8 @@ get(name: string): DomainImpl | undefined async closeAll(): Promise ``` +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) ## `ctx.subagents` — `SubagentService` @@ -2473,6 +2483,8 @@ flush?(): void abstract shutdown(): Promise ``` +Types: [TelemetryRecord](../subsystems/telemetry.md) + Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts) ## `ctx.tokenMeter` — `TokenMeterService` @@ -2885,7 +2897,7 @@ archiveSession(sessionId: SessionId): Promise async resolveByPath(path: string): Promise ``` -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) diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index 8988609cdf..f0df40a866 100644 --- a/docs/subsystems/client-modules.i18n.yaml +++ b/docs/subsystems/client-modules.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md -client-modules.md: bd5ecdc85ea26b040e1705e373ceac4e7cb17b3c -client-modules.zh.md: 5da7740e4c9272a3516f1a28712be7c95f29a6b3 +client-modules.md: 5dc89a87db7e79e4a297002fa059fdeffcceda20 +client-modules.zh.md: b3aa9c2aecf8e051a41b7b05f05d364b9ff8d2a6 diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md index bd5ecdc85e..5dc89a87db 100644 --- a/docs/subsystems/client-modules.md +++ b/docs/subsystems/client-modules.md @@ -27,7 +27,7 @@ interface WebBootEntry { rev: string /** Package-name dependency edges, informational (preflight display / HMR diffing). */ inject?: string[] - /** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */ + /** Stage-one prefetch mark: load the script for factory registration during module-face boot. */ immediately?: boolean } ``` diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index 5da7740e4c..b3aa9c2aec 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -27,7 +27,7 @@ interface WebBootEntry { rev: string /** Package-name dependency edges, informational (preflight display / HMR diffing). */ inject?: string[] - /** Stage-one prefetch mark: fetch + execute (factory registration) during module-face boot. */ + /** Stage-one prefetch mark: load the script for factory registration during module-face boot. */ immediately?: boolean } ``` diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 8bf98e026d..a2af261bc6 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: 1d0bf6c61500817860dfdd452bddc25adbbeffcc -core.zh.md: 2b4766dcfcf1901de2bb8a8f9168888a67af7fba +core.md: 3a8ccbef4163eabba1cd287284eda68135f2db13 +core.zh.md: 0cd6a57971ee98c75b52dd45b6f648632dab8a90 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 1d0bf6c615..3a8ccbef41 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -51,7 +51,6 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t | [invariants.md](invariants.md) | the runtime-invariant registry: selection `Config`, `InvariantInstaller`/`InvariantFailure`, the empty-companion contract | | [http-server.md](http-server.md) | the HTTP carrier: `WebRouteKind`/`WebRoute`, match order, the static dist fallback, index taps | | [storage.md](storage.md) | the storage subsystem: the backend seam (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | -| [tui.md](tui.md) | the terminal-extension seam: `TuiOverlayRequest`/`Host`/`Session`, close reasons and outcomes, the modal queue | | [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship | | [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap | | [telemetry.md](telemetry.md) | the outbound reporting seam: `TelemetryRecord`/`TelemetrySeverity`, the `TelemetryBackend` contract, the `telemetry/record` redact waterfall | diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 2b4766dcfc..0cd6a57971 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -51,7 +51,6 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数 | [invariants.md](invariants.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件契约 | | [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、静态 dist 回退、index 转换 | | [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | -| [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 | | [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | | [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | | [telemetry.md](telemetry.md) | 对外上报 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 契约、`telemetry/record` 脱敏 waterfall | diff --git a/docs/subsystems/http-server.i18n.yaml b/docs/subsystems/http-server.i18n.yaml index 8369b94dac..6f1facfad7 100644 --- a/docs/subsystems/http-server.i18n.yaml +++ b/docs/subsystems/http-server.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/http-server.md -http-server.md: 565df72f2720a1211f50650d4b1bc8d6df4a007a -http-server.zh.md: 69e4a0bd7a1576737583625b23fac2ed05d8ca61 +http-server.md: 200d3eed683f203b65824ea3f62cf56ce3df912f +http-server.zh.md: 9a12d3769a93270810927075be5f236875688b91 diff --git a/docs/subsystems/http-server.md b/docs/subsystems/http-server.md index 565df72f27..200d3eed68 100644 --- a/docs/subsystems/http-server.md +++ b/docs/subsystems/http-server.md @@ -2,7 +2,7 @@ English | [中文](http-server.zh.md) -[dsh-host-webserver](../../packages/host/webserver) is the web-shape HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.httpServer`, a named-route registry plus index.html transform taps over a static dist fallback. It is not part of the agent-loop spine and not a capability seam — it knows no harness concepts, and every feature surface (the `/api` bridge, plugin bundles, the HMR event stream) is a route some other plugin registers ([layering note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)). Web (browser) shape only: Electron loads dist over `file://` and carries fetch over an IPC bridge, not this server. +[dsh-host-webserver](../../packages/host/webserver) is the web-shape HTTP carrier for the GUI host: a single `node:http` plugin providing `ctx.httpServer`, a named-route registry, index.html transform taps, and a single claimable fallback seat. It is not part of the agent-loop spine and not a capability seam — it knows no harness concepts, and every feature surface (the `/api` bridge, plugin bundles, the HMR event stream) is a route some other plugin registers ([layering note](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md)). Web (browser) shape only: Electron loads dist over `file://` and carries fetch over an IPC bridge, not this server. Source: [`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) @@ -24,23 +24,21 @@ interface WebRoute { } ``` -Match order is fixed: exact table first, then longest matching prefix, then the static dist fallback. Registration order carries no request-facing semantics — named routes are composed to be disjoint, and the fallback answers anything not yet claimed during the boot window. The fallback keeps locked semantics: non-GET/HEAD is 405, traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), and unknown extensions ship as octet-stream ([`static.ts`](../../packages/host/webserver/src/static.ts)). +Match order is fixed: exact table first, then longest matching prefix, then the registered fallback. Registration order carries no request-facing semantics — named routes are composed to be disjoint, and the fallback seat answers anything no named route claims; one owner only, a second registration throws. The shipped Web composition claims the seat with [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts), the SPA dist server with locked semantics: non-GET/HEAD is 405, traversal outside the dist root is 403, any miss falls back to `index.html` with HTTP 200 (SPA routing), and unknown extensions ship as octet-stream. ## Config ```ts type-equiv -/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */ +/** Gateway config: the listen address. */ interface Config { /** Listen host; the two supported values are loopback and all-interfaces. */ host: '127.0.0.1' | '0.0.0.0' /** Listen port; zero requests an OS-assigned port. */ port: number - /** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */ - distIndex: string } ``` -`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. `distIndex` is an assembly fact the composing app resolves and injects. +`host` accepts only `127.0.0.1` (default posture) and `0.0.0.0` (deliberate network exposure); there is no TLS, auth, or origin policy, so a non-loopback bind exposes the server to that network. The dist location is an assembly fact of the frontend plugin that claims the seat. ## The service diff --git a/docs/subsystems/http-server.zh.md b/docs/subsystems/http-server.zh.md index 69e4a0bd7a..9a12d3769a 100644 --- a/docs/subsystems/http-server.zh.md +++ b/docs/subsystems/http-server.zh.md @@ -24,23 +24,21 @@ interface WebRoute { } ``` -匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到静态 dist 回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,启动窗口期内尚未被认领的请求全部由回退应答。回退遵循固定语义:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 提供([`static.ts`](../../packages/host/webserver/src/static.ts))。 +匹配顺序固定:先查 exact 表,再取最长匹配前缀,最后落到已注册的回退。注册顺序不携带任何面向请求的语义:具名路由在组合上互不相交,任何未被具名路由认领的请求都由回退席位应答;席位只有一个所有者,第二次注册会抛出异常。发布的 Web 组合用 [`dsh-frontend-static`](../../packages/host/frontend-static/src/index.ts) 认领席位,即遵循固定语义的 SPA dist 服务器:非 GET/HEAD 返回 405,越出 dist 根目录的遍历返回 403,任何未命中都以 HTTP 200 回退到 `index.html`(SPA 路由),未知扩展名按 octet-stream 发送。 ## 配置 ```ts type-equiv -/** Gateway config: listen address plus the static dist anchor (injected by the composing app, never self-resolved). */ +/** Gateway config: the listen address. */ interface Config { /** Listen host; the two supported values are loopback and all-interfaces. */ host: '127.0.0.1' | '0.0.0.0' /** Listen port; zero requests an OS-assigned port. */ port: number - /** Absolute path of index.html inside the static root (dist location is workspace knowledge of the app). */ - distIndex: string } ``` -`host` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。`distIndex` 是组合应用解析后注入的组装事实。 +`host` 只接受 `127.0.0.1`(默认姿态)和 `0.0.0.0`(刻意的网络暴露);没有 TLS、认证或 origin 策略,因此绑定到非回环地址会把服务器暴露给该网络。dist 位置是认领席位的前端插件的组装事实。 ## 服务 diff --git a/docs/subsystems/permission.i18n.yaml b/docs/subsystems/permission.i18n.yaml index 284f29bb62..c63da5efdc 100644 --- a/docs/subsystems/permission.i18n.yaml +++ b/docs/subsystems/permission.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/permission.md -permission.md: 49dd51ff86d0dbce26fca0ed21527f71bb144dec -permission.zh.md: c2ab6ecdfb4b80511107bee41e080b06fae45f2c +permission.md: ec8cd4a923b617934f1612c31284103266aea4ef +permission.zh.md: 4534c33e2715e4a500344f547c7895128877f4fb diff --git a/docs/subsystems/permission.md b/docs/subsystems/permission.md index 49dd51ff86..ec8cd4a923 100644 --- a/docs/subsystems/permission.md +++ b/docs/subsystems/permission.md @@ -2,9 +2,9 @@ English | [中文](permission.zh.md) -The permission-preset layer of [dsh-permission](../../packages/ui/permission) (`ctx.permission`, `PermissionService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. It is one optional capability, not part of the agent-loop spine, and it owns no enforcement: execution, prompt narration, and replay keep reading their knob folds, and a preset switch only records intent and writes through each knob's canonical setter. The [package README](../../packages/ui/permission/README.md) owns composition status and limitations; the [sandbox switching design](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the rationale. +The permission-preset layer of [dsh-permission](../../packages/interaction/permission) (`ctx.permission`, `PermissionService`) bundles the two independent enforcement knobs — [sandbox mode](sandbox.md) (`sandbox/mode`) and [approval policy](approval.md) (`approval/policy`) — into named presets a client offers as one Permissions selector. It is one optional capability, not part of the agent-loop spine, and it owns no enforcement: execution, prompt narration, and replay keep reading their knob folds, and a preset switch only records intent and writes through each knob's canonical setter. The [package README](../../packages/interaction/permission/README.md) owns composition status and limitations; the [sandbox switching design](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) owns the rationale. -Source: [`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts) +Source: [`packages/interaction/permission/src/index.ts`](../../packages/interaction/permission/src/index.ts) ## The preset table @@ -25,7 +25,7 @@ interface PresetSpec { ``` ```ts type-equiv -/** The {@link PermissionService} config: the deployment's preset table. */ +/** The {@link PermissionService} config: preset table and composition default. */ interface Config { /** * The preset table: name → knob bundle. Defaults to `workspace-write` @@ -33,6 +33,11 @@ interface Config { * never). The name `custom` is reserved for the derived not-a-preset state. */ presets?: Record + /** + * Default for new sessions. When omitted, the preset matching the composed + * sandbox and approval defaults is used. + */ + defaultPreset?: string } ``` @@ -51,13 +56,13 @@ interface PresetOption { value: string /** The display label. */ name: string - /** One user-facing sentence on what the value means. */ + /** One user-facing sentence on what the value means; omitted when not configured. */ description?: string } ``` ## Switching and the `permission/preset` event -`set(session, name)` resolves the preset (unknown names throw), appends a log-only `permission/preset` event unless `name` is already the effective preset, then writes each knob through its own setter — `setSandboxMode` from [dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) and `setApprovalPolicy` from [dsh-user-approval](../../packages/ui/user-approval) — only when that knob's effective value changes. The selection event precedes the knob events in the same turn, and re-selecting the effective preset appends nothing at all. +`set(session, name)` resolves the preset (unknown names throw), appends a log-only `permission/preset` event unless `name` is already the effective preset, then writes each knob through its own setter — `setSandboxMode` from [dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) and `setApprovalPolicy` from [dsh-user-approval](../../packages/interaction/user-approval) — only when that knob's effective value changes. The selection event precedes the knob events in the same turn, and re-selecting the effective preset appends nothing at all. `permission/preset` is durable, log-only user intent: it stays out of the model transcript (the knob events own the model-visible consequences through their consumers), and it exists so `current()` can preserve WHICH preset the user chose when two presets share a bundle; `effectivePermissionPreset(events)` folds the last one, and replay needs no catch-up state. The complete event declaration is in the [persistence log event catalog](../persistence-catalog.md); the method signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxpermission--permissionservice). diff --git a/docs/subsystems/permission.zh.md b/docs/subsystems/permission.zh.md index c2ab6ecdfb..4534c33e27 100644 --- a/docs/subsystems/permission.zh.md +++ b/docs/subsystems/permission.zh.md @@ -2,9 +2,9 @@ [English](permission.md) | 中文 -[dsh-permission](../../packages/ui/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮(knob),即[沙箱模式](sandbox.md)(`sandbox/mode`)与[审批策略](approval.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个权限(Permissions)选择器提供。它是一项可选能力,不属于 agent loop(智能体循环)主干,也不拥有任何强制执行:执行、提示词叙述与回放仍然读取各自旋钮的折叠结果,预设切换只记录意图,并通过每个旋钮各自的规范 setter 写入。[包(package)README](../../packages/ui/permission/README.md) 负责组合状态与限制;[沙箱切换设计](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)负责决策依据。 +[dsh-permission](../../packages/interaction/permission) 的权限预设层(`ctx.permission`,`PermissionService`)把两个相互独立的强制执行旋钮(knob),即[沙箱模式](sandbox.md)(`sandbox/mode`)与[审批策略](approval.md)(`approval/policy`),捆绑成具名预设,供客户端作为单个权限(Permissions)选择器提供。它是一项可选能力,不属于 agent loop(智能体循环)主干,也不拥有任何强制执行:执行、提示词叙述与回放仍然读取各自旋钮的折叠结果,预设切换只记录意图,并通过每个旋钮各自的规范 setter 写入。[包(package)README](../../packages/interaction/permission/README.md) 负责组合状态与限制;[沙箱切换设计](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)负责决策依据。 -源码:[`packages/ui/permission/src/index.ts`](../../packages/ui/permission/src/index.ts) +源码:[`packages/interaction/permission/src/index.ts`](../../packages/interaction/permission/src/index.ts) ## 预设表 @@ -25,7 +25,7 @@ interface PresetSpec { ``` ```ts type-equiv -/** The {@link PermissionService} config: the deployment's preset table. */ +/** The {@link PermissionService} config: preset table and composition default. */ interface Config { /** * The preset table: name → knob bundle. Defaults to `workspace-write` @@ -33,6 +33,11 @@ interface Config { * never). The name `custom` is reserved for the derived not-a-preset state. */ presets?: Record + /** + * Default for new sessions. When omitted, the preset matching the composed + * sandbox and approval defaults is used. + */ + defaultPreset?: string } ``` @@ -51,13 +56,13 @@ interface PresetOption { value: string /** The display label. */ name: string - /** One user-facing sentence on what the value means. */ + /** One user-facing sentence on what the value means; omitted when not configured. */ description?: string } ``` ## 切换与 `permission/preset` 事件 -`set(session, name)` 解析预设(未知名称抛出异常),在 `name` 尚不是生效预设时追加一条仅记日志的 `permission/preset` 事件,然后通过各旋钮自己的 setter([dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) 的 `setSandboxMode` 与 [dsh-user-approval](../../packages/ui/user-approval) 的 `setApprovalPolicy`)写入,且仅当该旋钮的生效值发生变化时才写。同一轮次内,选择事件先于旋钮事件出现;重新选择当前生效的预设则什么都不追加。 +`set(session, name)` 解析预设(未知名称抛出异常),在 `name` 尚不是生效预设时追加一条仅记日志的 `permission/preset` 事件,然后通过各旋钮自己的 setter([dsh-sandbox-policy](../../packages/sandbox/sandbox-policy) 的 `setSandboxMode` 与 [dsh-user-approval](../../packages/interaction/user-approval) 的 `setApprovalPolicy`)写入,且仅当该旋钮的生效值发生变化时才写。同一轮次内,选择事件先于旋钮事件出现;重新选择当前生效的预设则什么都不追加。 `permission/preset` 是持久、仅记日志的用户意图:它不进入模型 transcript(文本记录),模型可见的后果由旋钮事件经各自消费方承担;它存在是为了在两个预设共享同一个旋钮组合时,让 `current()` 仍能保住用户选择的究竟是哪一个预设;`effectivePermissionPreset(events)` 折叠最后一条,回放不需要任何追赶状态。完整事件声明见[持久化日志事件目录](../persistence-catalog.md);方法签名见生成的[服务目录](../cordis-catalog/services.md#ctxpermission--permissionservice)。 diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index 3295167755..f82002420b 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md -plan.md: c8bb6c9b6a0bcfd20ed6eadece4552d49f3c757c -plan.zh.md: c8acedaef0724581227b57afbf81418a7e538aed +plan.md: 661a393b18f9154771c0b9358ad7f016a4fdec17 +plan.zh.md: 068b63fd9f3bf46b7962031bba154cc7fc7daafd diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index c8bb6c9b6a..661a393b18 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -12,7 +12,7 @@ Source: [`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/s ## Pending intent and the step-boundary flush -Because every session event is turn-enclosed, a user selection is held as pending intent until the next step boundary — within the same turn when one is open. `set(agent, active)` records the pending selection (a no-op when the target equals the logged-or-already-pending state), and `get(agent)` returns `{ active: boolean; pending?: boolean }` — the logged state shaping the current step, plus the optimistic selection awaiting a boundary. +Because every session event is turn-enclosed, a user selection is held as pending intent until the next step boundary — the next request derivation, in whichever turn it occurs (selection never forces continuation, so an intent recorded after a turn's final step lands in a later turn). `set(agent, active)` records the pending selection (a no-op when the target equals the logged-or-already-pending state), and `get(agent)` returns `{ active: boolean; pending?: boolean }` — the logged state shaping the current step, plus the optimistic selection awaiting a boundary. The sole flush point is a prepended `agent/step` listener — the loop's in-turn interception seam that runs before every request derivation, including turn 1 step 1 and request-recovery retries. Prompt admission itself never flushes: it happens pre-turn, where a `plan/mode` append would land outside any open turn, so a selection made at the prompt is landed by the first step boundary inside the turn it starts. The prepend means the flush runs before the downstream `agent/step` listener chain. A flush failure is contained — plan policy can never block a turn — and the failed append stays pending for a later boundary. A flushed user selection also narrates the switch as one plugin-sourced `user/message` notice, but only when the last logged request header described the other state, so the model is told exactly when its context changed and never redundantly. A pending selection made while idle is process-local and lost on exit before the next boundary ([README limitation](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work)). diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index c8acedaef0..068b63fd9f 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -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))。 diff --git a/docs/subsystems/telemetry.i18n.yaml b/docs/subsystems/telemetry.i18n.yaml index e97a6f2e27..2eb634bb2d 100644 --- a/docs/subsystems/telemetry.i18n.yaml +++ b/docs/subsystems/telemetry.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md -telemetry.md: 92235a26fe278dfd6ac2b2ea5703b5c0d837b336 -telemetry.zh.md: e076abd90bd5054ddbfd6fbe7b725ee45d50a038 +telemetry.md: cf570723650c32f048ee797923a58f09c777455d +telemetry.zh.md: 1bcaa86264e3afd741d66d6ee718946e6151496f diff --git a/docs/subsystems/telemetry.md b/docs/subsystems/telemetry.md index 92235a26fe..cf57072365 100644 --- a/docs/subsystems/telemetry.md +++ b/docs/subsystems/telemetry.md @@ -2,9 +2,9 @@ English | [中文](telemetry.zh.md) -Outbound session reporting, split as a [capability seam](../capability-seams.md): the seam ([dsh-session-telemetry](../../packages/telemetry/session-telemetry), `ctx.telemetry`) owns the capture points, the fixed chunk projection, the `telemetry/record` redaction waterfall, the handoff cursor, and the minimal backend contract; the backend a deployment loads ([dsh-session-telemetry-otel](../../packages/telemetry/session-telemetry-otel)) is the OpenTelemetry JS SDK's log pipeline configured verbatim. It is one optional capability, not part of the agent-loop spine, and nothing here reaches a model request. The boundary axiom — the harness's aspect ends at `emit()`; batching, retry, queueing, and loss policy belong to the reporting SDK — and the rejected alternatives are pinned in the [revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md); the capture points, cursor, and projection contracts live in the [seam README](../../packages/telemetry/session-telemetry/README.md). +Outbound session reporting, split as a [capability seam](../capability-seams.md): the seam ([dsh-session-telemetry](../../packages/session/session-telemetry), `ctx.telemetry`) owns the capture points, the fixed chunk projection, the `telemetry/record` redaction waterfall, the handoff cursor, and the minimal backend contract; the backend a deployment loads ([dsh-session-telemetry-otel](../../packages/session/session-telemetry-otel)) is the OpenTelemetry JS SDK's log pipeline configured verbatim. It is one optional capability, not part of the agent-loop spine, and nothing here reaches a model request. The boundary axiom — the harness's aspect ends at `emit()`; batching, retry, queueing, and loss policy belong to the reporting SDK — and the rejected alternatives are pinned in the [revival Agent Note](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md); the capture points, cursor, and projection contracts live in the [seam README](../../packages/session/session-telemetry/README.md). -Source: [`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts) +Source: [`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts) ## The logical record @@ -12,7 +12,7 @@ Source: [`packages/telemetry/session-telemetry/src/index.ts`](../../packages/tel /** * Severity of a telemetry record, pre-mapped at capture so a receiver can * alert with zero configuration: `error` for events whose own outcome flag - * says so (`tool/result.isError`, `turn/end` error reasons) and for + * says so (the tool-result block's `isError`, `turn/end` error reasons) and for * `agent-error` operational records. Captured events otherwise default to * `info`; `warn` remains available to `telemetry/record` policies and * backends. @@ -69,9 +69,10 @@ interface TelemetryBackend { /** * Hand one record to the backend's pipeline. MUST be a non-blocking * enqueue — the coordinator calls this synchronously from the - * `session/event` hot path, so anything slower than a queue push would tax - * the agent loop. Errors thrown here are contained by the coordinator and - * logged; they never reach the loop. + * `session/event` hot path or an explicit canonical-log capture, so anything + * slower than a queue push would tax the agent loop or feedback handling. + * Errors thrown here are contained by the coordinator and logged; they + * never reach the loop. * @param record - the logical record to report; owned by the backend after the call. */ emit(record: TelemetryRecord): void @@ -96,6 +97,8 @@ interface TelemetryBackend { * coordinator emits its dispose-time `shutdown` markers immediately before * calling this). Awaited by the coordinator's dispose; a rejection is * logged as a warning and never fails application teardown. + * The coordinator captures dispose-time shutdown markers immediately before + * this call for live capture; on-demand capture creates no ops records. * @returns resolves when the backend's pipeline has quiesced. */ shutdown(): Promise diff --git a/docs/subsystems/telemetry.zh.md b/docs/subsystems/telemetry.zh.md index e076abd90b..1bcaa86264 100644 --- a/docs/subsystems/telemetry.zh.md +++ b/docs/subsystems/telemetry.zh.md @@ -2,9 +2,9 @@ [English](telemetry.md) | 中文 -对外的会话上报,拆分为一项[能力 seam](../capability-seams.md):seam 一侧([dsh-session-telemetry](../../packages/telemetry/session-telemetry),`ctx.telemetry`)拥有捕获点、固定分片投影、`telemetry/record` 脱敏 waterfall(瀑布式事件)、handoff 游标与最小后端契约;部署方加载的后端([dsh-session-telemetry-otel](../../packages/telemetry/session-telemetry-otel))则是原样配置的 OpenTelemetry JS SDK 日志流水线。它是一项可选能力,不属于 agent loop(智能体循环)主干,这里也没有任何内容会进入模型请求。边界公理(harness 的职责止于 `emit()`;批处理、重试、排队与丢失策略都属于上报 SDK)连同被否决的替代方案,均已在[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)中定案;捕获点、游标与投影的契约见 [seam README](../../packages/telemetry/session-telemetry/README.md)。 +对外的会话上报,拆分为一项[能力 seam](../capability-seams.md):seam 一侧([dsh-session-telemetry](../../packages/session/session-telemetry),`ctx.telemetry`)拥有捕获点、固定分片投影、`telemetry/record` 脱敏 waterfall(瀑布式事件)、handoff 游标与最小后端契约;部署方加载的后端([dsh-session-telemetry-otel](../../packages/session/session-telemetry-otel))则是原样配置的 OpenTelemetry JS SDK 日志流水线。它是一项可选能力,不属于 agent loop(智能体循环)主干,这里也没有任何内容会进入模型请求。边界公理(harness 的职责止于 `emit()`;批处理、重试、排队与丢失策略都属于上报 SDK)连同被否决的替代方案,均已在[复活 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-23-session-telemetry-otel-revival.md)中定案;捕获点、游标与投影的契约见 [seam README](../../packages/session/session-telemetry/README.md)。 -源码:[`packages/telemetry/session-telemetry/src/index.ts`](../../packages/telemetry/session-telemetry/src/index.ts) +源码:[`packages/session/session-telemetry/src/index.ts`](../../packages/session/session-telemetry/src/index.ts) ## 逻辑记录 @@ -12,7 +12,7 @@ /** * Severity of a telemetry record, pre-mapped at capture so a receiver can * alert with zero configuration: `error` for events whose own outcome flag - * says so (`tool/result.isError`, `turn/end` error reasons) and for + * says so (the tool-result block's `isError`, `turn/end` error reasons) and for * `agent-error` operational records. Captured events otherwise default to * `info`; `warn` remains available to `telemetry/record` policies and * backends. @@ -69,9 +69,10 @@ interface TelemetryBackend { /** * Hand one record to the backend's pipeline. MUST be a non-blocking * enqueue — the coordinator calls this synchronously from the - * `session/event` hot path, so anything slower than a queue push would tax - * the agent loop. Errors thrown here are contained by the coordinator and - * logged; they never reach the loop. + * `session/event` hot path or an explicit canonical-log capture, so anything + * slower than a queue push would tax the agent loop or feedback handling. + * Errors thrown here are contained by the coordinator and logged; they + * never reach the loop. * @param record - the logical record to report; owned by the backend after the call. */ emit(record: TelemetryRecord): void @@ -96,6 +97,8 @@ interface TelemetryBackend { * coordinator emits its dispose-time `shutdown` markers immediately before * calling this). Awaited by the coordinator's dispose; a rejection is * logged as a warning and never fails application teardown. + * The coordinator captures dispose-time shutdown markers immediately before + * this call for live capture; on-demand capture creates no ops records. * @returns resolves when the backend's pipeline has quiesced. */ shutdown(): Promise diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml index a50df35f91..6a6488206c 100644 --- a/docs/subsystems/workspace.i18n.yaml +++ b/docs/subsystems/workspace.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/workspace.md -workspace.md: bade3526964055d5bf4448265c5898f1e66a1fce -workspace.zh.md: b0437e42b644c1ce11d9a667972f7b236cf4caa1 +workspace.md: 5614631e828e51f3b515ae4af17451bd9eda1dbb +workspace.zh.md: 11e8f0207a8db8b7612d6672cfe744523490aef7 diff --git a/docs/subsystems/workspace.md b/docs/subsystems/workspace.md index bade352696..5614631e82 100644 --- a/docs/subsystems/workspace.md +++ b/docs/subsystems/workspace.md @@ -68,7 +68,9 @@ interface Workspace { /** * Prepend a session to this workspace's candidate account. An already - * accounted id resolves without writing. A new id's live or persisted + * accounted id resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs. A new id's + * live or persisted * header cwd must resolve to an existing directory equal to {@link path}; * unknown ids, missing or invalid cwd values, and mismatches reject without * writing. @@ -82,7 +84,9 @@ interface Workspace { * with an anchor the session lands before it, without one it appends to the * end. Only the moved id changes position. A session or anchor absent from * the account rejects without writing; a move to the current position - * resolves without writing (decided on the domain write chain). + * resolves without writing, aside from the durable filtered-candidate + * prune every accepted mutation performs; decided on the domain write + * chain. * @param sessionId - The accounted session to move. * @param beforeSessionId - Accounted anchor to insert before; omitted appends. * @returns resolution after durability. @@ -91,8 +95,9 @@ interface Workspace { /** * Remove a session from this workspace's account. Idempotent: an id not on - * the account resolves without writing (decided on the domain write chain, - * like attach). Never touches the session's own stored log. + * the account resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs; decided on + * the domain write chain like attach. Never touches the session's own stored log. * @param sessionId - The session to remove. * @returns resolution after durability. */ diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md index b0437e42b6..11e8f0207a 100644 --- a/docs/subsystems/workspace.zh.md +++ b/docs/subsystems/workspace.zh.md @@ -68,7 +68,9 @@ interface Workspace { /** * Prepend a session to this workspace's candidate account. An already - * accounted id resolves without writing. A new id's live or persisted + * accounted id resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs. A new id's + * live or persisted * header cwd must resolve to an existing directory equal to {@link path}; * unknown ids, missing or invalid cwd values, and mismatches reject without * writing. @@ -82,7 +84,9 @@ interface Workspace { * with an anchor the session lands before it, without one it appends to the * end. Only the moved id changes position. A session or anchor absent from * the account rejects without writing; a move to the current position - * resolves without writing (decided on the domain write chain). + * resolves without writing, aside from the durable filtered-candidate + * prune every accepted mutation performs; decided on the domain write + * chain. * @param sessionId - The accounted session to move. * @param beforeSessionId - Accounted anchor to insert before; omitted appends. * @returns resolution after durability. @@ -91,8 +95,9 @@ interface Workspace { /** * Remove a session from this workspace's account. Idempotent: an id not on - * the account resolves without writing (decided on the domain write chain, - * like attach). Never touches the session's own stored log. + * the account resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs; decided on + * the domain write chain like attach. Never touches the session's own stored log. * @param sessionId - The session to remove. * @returns resolution after durability. */ diff --git a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts index 30ef60e8fe..1b6589b6cf 100644 --- a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts @@ -152,11 +152,11 @@ describe.skip('gen-cordis-catalog collectEvents', { timeout: 60_000 }, () => { it('accepts linked, foundation, generic-parameter, and explicitly exempt signature types', () => { 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\'(value: Readonly, assembly: PromptAssembly, signal: AbortSignal): Promise', + ' /**\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\'(value: Readonly, preset: PresetSpec, signal: AbortSignal): Promise', )) expect(events).toHaveLength(1) 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', () => { diff --git a/packages/workspace/workspace/README.i18n.yaml b/packages/workspace/workspace/README.i18n.yaml index 7ba60b4109..63d5ce0e8e 100644 --- a/packages/workspace/workspace/README.i18n.yaml +++ b/packages/workspace/workspace/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/workspace/workspace/README.md README.md: 057765e38de9cc700210eb8edeb1ddc7ffc861ff -README.zh.md: f3be9611eba45db3719ce76ad6625f4787fab9ac +README.zh.md: 7416875dbf2ee1652f6e1fa1663144d7407a1ae7 diff --git a/packages/workspace/workspace/README.zh.md b/packages/workspace/workspace/README.zh.md index f3be9611eb..7416875dbf 100644 --- a/packages/workspace/workspace/README.zh.md +++ b/packages/workspace/workspace/README.zh.md @@ -12,6 +12,7 @@ DeepSeek Harness 的 Workspace 实体注册表(`ctx.workspace`):通过领 - `ctx.workspace.get(id)`/`list()`/`resolveByPath(path)`:由缓存提供的查找。`list()` 为同步操作,并遵循持久注册表顺序;`resolveByPath` 为异步操作,因为它采用相同的 `realpath` 规范化方式,并会拒绝缺失路径,而不是创建路径。 - `ctx.workspace.delete(id)`:只移除 Workspace 注册记录、对应的持久顺序条目及会话归属记录。未知 id 返回 `false`,成功移除记录则返回 `true`。目录、用户文件、活跃会话和持久化会话日志绝不受影响,因此相关会话会进入 Ungrouped。表写入失败时会恢复原顺序和此前发布的实体。 - `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。在该字段出现之前写入的状态解析为一个空集合。 - `Workspace.sessionIds`:按持久候选顺序提供同步 id 加规范 cwd 成员投影。缺失头部、无效 cwd 值和不匹配情况都被过滤;下一次 workspace 变更会剪除它们。如果同一存储介质将一个会话索引到两个 workspace 下、用两条记录声明同一路径,或偏离持久 workspace 顺序,启动会被拒绝。 - `Workspace.status()`:未缓存的目录检查,返回 `'ok' | 'missing-dir'`;目录缺失绝不会改动记录。 diff --git a/packages/workspace/workspace/src/types.ts b/packages/workspace/workspace/src/types.ts index 09d37213cc..555e94d45d 100644 --- a/packages/workspace/workspace/src/types.ts +++ b/packages/workspace/workspace/src/types.ts @@ -59,7 +59,9 @@ export interface Workspace { /** * Prepend a session to this workspace's candidate account. An already - * accounted id resolves without writing. A new id's live or persisted + * accounted id resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs. A new id's + * live or persisted * header cwd must resolve to an existing directory equal to {@link path}; * unknown ids, missing or invalid cwd values, and mismatches reject without * writing. @@ -73,7 +75,9 @@ export interface Workspace { * with an anchor the session lands before it, without one it appends to the * end. Only the moved id changes position. A session or anchor absent from * the account rejects without writing; a move to the current position - * resolves without writing (decided on the domain write chain). + * resolves without writing, aside from the durable filtered-candidate + * prune every accepted mutation performs; decided on the domain write + * chain. * @param sessionId - The accounted session to move. * @param beforeSessionId - Accounted anchor to insert before; omitted appends. * @returns resolution after durability. @@ -82,8 +86,9 @@ export interface Workspace { /** * Remove a session from this workspace's account. Idempotent: an id not on - * the account resolves without writing (decided on the domain write chain, - * like attach). Never touches the session's own stored log. + * the account resolves without writing, aside from the durable + * filtered-candidate prune every accepted mutation performs; decided on + * the domain write chain like attach. Never touches the session's own stored log. * @param sessionId - The session to remove. * @returns resolution after durability. */ diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index b3fdfb7546..3cc277152e 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -1,32 +1,25 @@ /** - * Generate the Cordis event and service catalogs from static declarations. - * The walk enforces event modes, JSDoc parameter/return completeness, and - * signature type-link coverage; inherited Cordis services come from the - * curated table below. `--check` verifies both committed artifacts. + * Generate committed Cordis artifacts from the Typert catalog projector and + * the independent vendored-core projector. */ -import { globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' -import { dirname, resolve, sep } from 'node:path' -import ts from 'typescript' +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +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 { 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 OUT_EVENTS = 'docs/cordis-catalog/events.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 - * doc-typecheck, since a bare signature fragment is not standalone-compilable). */ -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 = { +/** One primary subsystems page per project type used by a generated signature. */ +export const LINK_MAP: Readonly> = { Agent: 'core.md', AgentCancelCause: 'core.md', AgentOptions: 'core.md', @@ -35,6 +28,7 @@ export const LINK_MAP: Record = { ContinuationDecision: 'core.md', ContinuationStop: 'core.md', GenerateOptions: 'core.md', + InboxItem: 'core.md', InboxPlacement: 'core.md', MessageId: 'core.md', HookContext: 'core.md', @@ -57,9 +51,7 @@ export const LINK_MAP: Record = { UserMessage: 'session.md', PreStepDecision: 'core.md', PreStepContext: 'core.md', - PromptDecision: 'core.md', RequestErrorAction: 'core.md', - RequestError: 'core.md', RequestFailureContext: 'core.md', PreparedReferencedMessage: 'session-reference.md', SessionReferenceCandidate: 'session-reference.md', @@ -116,6 +108,7 @@ export const LINK_MAP: Record = { PreparedLlmCall: 'llm-streaming.md', LlmService: 'llm-streaming.md', StreamChunk: 'llm-streaming.md', + SkillProviderControl: 'skills.md', CreateSessionOptions: 'persistence.md', PrepareSessionOptions: 'persistence.md', SessionHeader: 'persistence.md', @@ -168,9 +161,11 @@ export const LINK_MAP: Record = { SessionTitleObservationResult: 'session-query.md', SessionTitleProvider: 'session-title.md', SessionTitleSnapshot: 'session-title.md', + SkillCatalogSnapshot: 'skills.md', SkillDefinition: 'skills.md', SkillLookupOptions: 'skills.md', SkillProvider: 'skills.md', + SkillProviderObservation: 'skills.md', SkillRegistration: 'skills.md', SkillSummary: 'skills.md', SaveTextSpill: 'spill.md', @@ -237,8 +232,6 @@ export const LINK_MAP: Record = { WebSearchRequest: 'web.md', WebSearchResult: 'web.md', WorkflowRun: 'workflow.md', - WorkflowRunInfo: 'workflow.md', - WorkflowStartRequest: 'workflow.md', PresetOption: 'permission.md', PresetSpec: 'permission.md', InvariantInstaller: 'invariants.md', @@ -253,14 +246,17 @@ export const LINK_MAP: Record = { WorkspaceId: 'workspace.md', WebBootGraph: 'client-modules.md', TelemetryRecord: 'telemetry.md', + WorkflowRunInfo: 'workflow.md', + WorkflowStartRequest: 'workflow.md', } -/** TypeScript lib and pinned framework types that have no repository-owned data page. */ -const FOUNDATION_TYPE_NAMES = new Set([ +/** TypeScript lib and pinned framework types with no repository-owned data page. */ +export const FOUNDATION_TYPE_NAMES: ReadonlySet = new Set([ 'AbortSignal', 'AsyncIterable', 'Context', 'Error', + 'Map', 'Partial', 'Pick', 'Promise', @@ -269,7 +265,7 @@ const FOUNDATION_TYPE_NAMES = new Set([ ]) /** Project types deliberately documented outside the subsystems catalog. */ -const TYPE_LINK_EXEMPTIONS: Readonly> = { +export const TYPE_LINK_EXEMPTIONS: Readonly> = { AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md', 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', @@ -281,6 +277,7 @@ const TYPE_LINK_EXEMPTIONS: Readonly> = { 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', 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', 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', @@ -318,401 +315,51 @@ const TYPE_LINK_EXEMPTIONS: Readonly> = { WorkflowResultInfo: 'event-local snapshot is owned by packages/workflow/workflow/src/index.ts', } -/** Collect named references from parameter, generic-constraint/default, and return types. */ -function signatureTypeNames(member: ts.MethodSignature | ts.MethodDeclaration, sf: ts.SourceFile): string[] { - const declared = new Set(member.typeParameters?.map(parameter => parameter.name.text) ?? []) - const referenced = new Set() - const visit = (node: ts.Node): void => { - if (ts.isTypeReferenceNode(node)) referenced.add(node.typeName.getText(sf)) - if (ts.isTypeQueryNode(node)) referenced.add(node.exprName.getText(sf)) - ts.forEachChild(node, visit) - } - for (const parameter of member.typeParameters ?? []) { - if (parameter.constraint) visit(parameter.constraint) - if (parameter.default) visit(parameter.default) - } - for (const parameter of member.parameters) { - if (parameter.type) visit(parameter.type) - } - if (member.type) visit(member.type) - return [...referenced].filter(name => !declared.has(name)).sort() +/** Repository data policy consumed by the Cordis catalog projector. */ +export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = { + linkedTypePages: LINK_MAP, + foundationTypeNames: FOUNDATION_TYPE_NAMES, + typeLinkExemptions: TYPE_LINK_EXEMPTIONS, + inheritedEvents: [ + { 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' }, + ], + 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. */ -function checkTypeLinks( - 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.` 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. +/** CLI entry: default writes every artifact; `--check` reports stale files. + * @returns nothing; writes files or reports freshness through the process. */ -function jsDocText(text: string, sf: ts.SourceFile, node: ts.Node): string { - const raw = rawJsDoc(text, node) - 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.` 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() - 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 = [ - '', - '', -] - -/** 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.` 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.` 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 { +export function main(): void { + const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY) const outputs: [string, string][] = [ - [OUT_EVENTS, renderEvents(collectEvents())], - [OUT_SERVICES, renderServices(collectServices())], + [OUT_EVENTS, renderEvents([...model.events], CORDIS_CATALOG_POLICY)], + [OUT_SERVICES, renderServices([...model.services], CORDIS_CATALOG_POLICY)], + [OUT_RUNTIME_API, projector.renderRuntimeApi(model)], ...renderCordisCoreApiPages(), ] if (process.argv.includes('--check')) { @@ -722,9 +369,7 @@ function main(): void { try { committed = readFileSync(resolve(root, out), 'utf8') } catch { - // Only ENOENT (not yet generated) is expected; a present-but-unreadable - // file is not a state this repo produces. Either way the remedy is the - // same — regenerate — so treat a read failure as "stale". + // Only ENOENT is expected; either read failure has the same remedy. committed = null } if (committed !== content) stale.push(out) @@ -745,7 +390,4 @@ function main(): void { 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() diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index e1f80b96b6..2bc097c88c 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -199,7 +199,7 @@ { "doc": "docs/subsystems/goal.md", "symbol": "GoalView", - "source": "packages/goal/goal/src/domain.ts" + "source": "packages/goal/goal/src/types.ts" }, { "doc": "docs/subsystems/goal.md", @@ -219,12 +219,12 @@ { "doc": "docs/subsystems/goal.md", "symbol": "CreateGoalRequest", - "source": "packages/goal/goal/src/domain.ts" + "source": "packages/goal/goal/src/types.ts" }, { "doc": "docs/subsystems/goal.md", "symbol": "EditGoalRequest", - "source": "packages/goal/goal/src/domain.ts" + "source": "packages/goal/goal/src/types.ts" }, { "doc": "docs/subsystems/goal.md", @@ -1508,7 +1508,7 @@ { "doc": "docs/subsystems/permission.md", "symbol": "PresetOption", - "source": "packages/interaction/permission/src/index.ts" + "source": "packages/interaction/permission/src/types.ts" }, { "doc": "docs/subsystems/plan.md", @@ -1575,31 +1575,6 @@ "symbol": "DomainChanged", "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", "symbol": "WorkspaceId", @@ -1623,17 +1598,17 @@ { "doc": "docs/subsystems/telemetry.md", "symbol": "TelemetrySeverity", - "source": "packages/telemetry/session-telemetry/src/index.ts" + "source": "packages/session/session-telemetry/src/index.ts" }, { "doc": "docs/subsystems/telemetry.md", "symbol": "TelemetryRecord", - "source": "packages/telemetry/session-telemetry/src/index.ts" + "source": "packages/session/session-telemetry/src/index.ts" }, { "doc": "docs/subsystems/telemetry.md", "symbol": "TelemetryBackend", - "source": "packages/telemetry/session-telemetry/src/index.ts" + "source": "packages/session/session-telemetry/src/index.ts" }, { "doc": "docs/subsystems/typert.md", From f7323354bb9be166620fb47d106afb8abc53c315 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:40:58 +0800 Subject: [PATCH 16/19] docs: generate each subsystem's cordis surface into its own page; delete the flat catalogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuild of the region machinery (PR3) on the post-#904 Typert projection: renderPageRegion/renderInheritedPage live in dsh-typert-generator beside the projection; scripts/gen-cordis-catalog.ts owns the curated SERVICE_PAGE / EVENT_SCOPE_PAGE / SERVICE_WALK_EXEMPTIONS / LINK_MAP partition (fail-loud in both directions, with the independent Context-merge scan backstopping the projection's blind spot), spliceRegion, and the guarded pair auto-record. docs/cordis-catalog/ is deleted: the flat events/services catalogs dissolve into per-page regions and docs/cordis-catalog/core moves to docs/cordis-api/ with the inherited tier as its own generated page. The partition absorbs the post-regrouping surface: ctx.typert → invariants.md, ctx.directoryPicker → workspace.md, skills/* events → skills.md, and the four launcher-provided tui accessor values join the named exemptions. --- ...bagent-provider-lifecycle-events.i18n.yaml | 4 +- ...7-05-subagent-provider-lifecycle-events.md | 2 +- ...5-subagent-provider-lifecycle-events.zh.md | 2 +- .../2026-07-08-agent-scope-contexts.i18n.yaml | 4 +- .../2026-07-08-agent-scope-contexts.md | 2 +- .../2026-07-08-agent-scope-contexts.zh.md | 2 +- .../feature/2026-07-05-skill-system.i18n.yaml | 4 +- .../feature/2026-07-05-skill-system.md | 2 +- .../feature/2026-07-05-skill-system.zh.md | 2 +- ...subsystem-cordis-surface-regions.i18n.yaml | 6 + ...28-per-subsystem-cordis-surface-regions.md | 36 + ...per-subsystem-cordis-surface-regions.zh.md | 36 + docs/AGENTS.md | 2 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- docs/config-catalog.md | 2 +- .../core => cordis-api}/context.md | 40 +- .../core => cordis-api}/events.md | 20 +- .../core => cordis-api}/fiber.md | 44 +- docs/cordis-api/inherited.md | 39 + .../core => cordis-api}/registry.md | 8 +- .../core => cordis-api}/service.md | 18 +- docs/cordis-catalog/events.md | 1100 ------- docs/cordis-catalog/services.md | 2917 ----------------- docs/cordis-primer.i18n.yaml | 4 +- docs/cordis-primer.md | 2 +- docs/cordis-primer.zh.md | 2 +- docs/cordis-tutorial/03-services.i18n.yaml | 4 +- docs/cordis-tutorial/03-services.md | 2 +- docs/cordis-tutorial/03-services.zh.md | 2 +- docs/cordis-tutorial/04-events.i18n.yaml | 4 +- docs/cordis-tutorial/04-events.md | 4 +- docs/cordis-tutorial/04-events.zh.md | 4 +- .../07-into-the-harness.i18n.yaml | 4 +- docs/cordis-tutorial/07-into-the-harness.md | 2 +- .../cordis-tutorial/07-into-the-harness.zh.md | 2 +- docs/cordis-tutorial/index.i18n.yaml | 4 +- docs/cordis-tutorial/index.md | 2 +- docs/cordis-tutorial/index.zh.md | 2 +- docs/graph-atlas.md | 2 +- docs/i18n/style-samples.md | 4 +- docs/persistence-catalog.md | 2 +- docs/subsystems/approval.i18n.yaml | 4 +- docs/subsystems/approval.md | 82 + docs/subsystems/approval.zh.md | 82 + docs/subsystems/bash.i18n.yaml | 6 +- docs/subsystems/bash.md | 82 + docs/subsystems/bash.zh.md | 82 + docs/subsystems/client-modules.i18n.yaml | 4 +- docs/subsystems/client-modules.md | 57 +- docs/subsystems/client-modules.zh.md | 57 +- docs/subsystems/code-runtime.i18n.yaml | 6 +- docs/subsystems/code-runtime.md | 30 + docs/subsystems/code-runtime.zh.md | 30 + docs/subsystems/commands.i18n.yaml | 4 +- docs/subsystems/commands.md | 86 + docs/subsystems/commands.zh.md | 86 + docs/subsystems/compaction.i18n.yaml | 4 +- docs/subsystems/compaction.md | 116 + docs/subsystems/compaction.zh.md | 116 + docs/subsystems/core.i18n.yaml | 4 +- docs/subsystems/core.md | 546 ++- docs/subsystems/core.zh.md | 546 ++- docs/subsystems/credentials.i18n.yaml | 4 +- docs/subsystems/credentials.md | 83 + docs/subsystems/credentials.zh.md | 83 + docs/subsystems/filesystem.i18n.yaml | 6 +- docs/subsystems/filesystem.md | 208 +- docs/subsystems/filesystem.zh.md | 208 +- docs/subsystems/goal.i18n.yaml | 6 +- docs/subsystems/goal.md | 132 + docs/subsystems/goal.zh.md | 132 + docs/subsystems/http-server.i18n.yaml | 4 +- docs/subsystems/http-server.md | 61 + docs/subsystems/http-server.zh.md | 61 + docs/subsystems/invariants.i18n.yaml | 4 +- docs/subsystems/invariants.md | 29 + docs/subsystems/invariants.zh.md | 29 + docs/subsystems/llm-streaming.i18n.yaml | 4 +- docs/subsystems/llm-streaming.md | 188 ++ docs/subsystems/llm-streaming.zh.md | 188 ++ docs/subsystems/permission.i18n.yaml | 4 +- docs/subsystems/permission.md | 65 +- docs/subsystems/permission.zh.md | 65 +- docs/subsystems/persistence.i18n.yaml | 4 +- docs/subsystems/persistence.md | 132 + docs/subsystems/persistence.zh.md | 132 + docs/subsystems/plan.i18n.yaml | 4 +- docs/subsystems/plan.md | 49 +- docs/subsystems/plan.zh.md | 49 +- docs/subsystems/pty.i18n.yaml | 6 +- docs/subsystems/pty.md | 93 + docs/subsystems/pty.zh.md | 93 + docs/subsystems/sandbox.i18n.yaml | 6 +- docs/subsystems/sandbox.md | 62 + docs/subsystems/sandbox.zh.md | 62 + docs/subsystems/session-projection.i18n.yaml | 6 + docs/subsystems/session-projection.md | 262 ++ docs/subsystems/session-projection.zh.md | 262 ++ docs/subsystems/session-query.i18n.yaml | 6 +- docs/subsystems/session-query.md | 138 + docs/subsystems/session-query.zh.md | 138 + docs/subsystems/session-reference.i18n.yaml | 4 +- docs/subsystems/session-reference.md | 41 + docs/subsystems/session-reference.zh.md | 41 + docs/subsystems/session-title.i18n.yaml | 4 +- docs/subsystems/session-title.md | 58 + docs/subsystems/session-title.zh.md | 58 + docs/subsystems/session.i18n.yaml | 4 +- docs/subsystems/session.md | 246 +- docs/subsystems/session.zh.md | 246 +- docs/subsystems/settings.i18n.yaml | 4 +- docs/subsystems/settings.md | 148 + docs/subsystems/settings.zh.md | 148 + docs/subsystems/skills.i18n.yaml | 4 +- docs/subsystems/skills.md | 90 + docs/subsystems/skills.zh.md | 90 + docs/subsystems/spill.i18n.yaml | 6 +- docs/subsystems/spill.md | 32 + docs/subsystems/spill.zh.md | 32 + docs/subsystems/storage.i18n.yaml | 4 +- docs/subsystems/storage.md | 110 +- docs/subsystems/storage.zh.md | 110 +- docs/subsystems/subagent.i18n.yaml | 6 +- docs/subsystems/subagent.md | 266 ++ docs/subsystems/subagent.zh.md | 266 ++ docs/subsystems/subprocess.i18n.yaml | 6 +- docs/subsystems/subprocess.md | 77 +- docs/subsystems/subprocess.zh.md | 77 +- docs/subsystems/system-prompt.i18n.yaml | 4 +- docs/subsystems/system-prompt.md | 110 + docs/subsystems/system-prompt.zh.md | 110 + docs/subsystems/tasks.i18n.yaml | 6 +- docs/subsystems/tasks.md | 106 + docs/subsystems/tasks.zh.md | 106 + docs/subsystems/telemetry.i18n.yaml | 4 +- docs/subsystems/telemetry.md | 72 +- docs/subsystems/telemetry.zh.md | 72 +- docs/subsystems/token-meter.i18n.yaml | 6 +- docs/subsystems/token-meter.md | 47 + docs/subsystems/token-meter.zh.md | 47 + docs/subsystems/tools.i18n.yaml | 4 +- docs/subsystems/tools.md | 238 ++ docs/subsystems/tools.zh.md | 238 ++ docs/subsystems/typert.i18n.yaml | 6 +- docs/subsystems/typert.md | 93 + docs/subsystems/typert.zh.md | 93 + docs/subsystems/user-interaction.i18n.yaml | 4 +- docs/subsystems/user-interaction.md | 44 + docs/subsystems/user-interaction.zh.md | 44 + docs/subsystems/web.i18n.yaml | 6 +- docs/subsystems/web.md | 67 + docs/subsystems/web.zh.md | 67 + docs/subsystems/workflow.i18n.yaml | 6 +- docs/subsystems/workflow.md | 152 +- docs/subsystems/workflow.zh.md | 152 +- docs/subsystems/workspace.i18n.yaml | 4 +- docs/subsystems/workspace.md | 95 +- docs/subsystems/workspace.zh.md | 95 +- docs/tool-catalog.md | 2 +- docs/user/develop/framework/events.i18n.yaml | 4 +- docs/user/develop/framework/events.md | 2 +- docs/user/develop/framework/events.zh.md | 2 +- docs/user/develop/framework/service.i18n.yaml | 4 +- docs/user/develop/framework/service.md | 2 +- docs/user/develop/framework/service.zh.md | 2 +- packages/core/agent-loop/README.i18n.yaml | 4 +- packages/core/agent-loop/README.md | 2 +- packages/core/agent-loop/README.zh.md | 2 +- packages/core/agent/README.i18n.yaml | 4 +- packages/core/agent/README.md | 2 +- packages/core/agent/README.zh.md | 2 +- packages/core/session/README.i18n.yaml | 4 +- packages/core/session/README.md | 2 +- packages/core/session/README.zh.md | 2 +- packages/core/system-prompt/README.i18n.yaml | 4 +- packages/core/system-prompt/README.md | 2 +- packages/core/system-prompt/README.zh.md | 2 +- packages/core/tools/README.i18n.yaml | 4 +- packages/core/tools/README.md | 4 +- packages/core/tools/README.zh.md | 4 +- packages/fs/fs/README.i18n.yaml | 4 +- packages/fs/fs/README.md | 2 +- packages/fs/fs/README.zh.md | 2 +- packages/goal/goal/README.i18n.yaml | 4 +- packages/goal/goal/README.md | 2 +- packages/goal/goal/README.zh.md | 2 +- .../user-approval/README.i18n.yaml | 4 +- packages/interaction/user-approval/README.md | 2 +- .../interaction/user-approval/README.zh.md | 2 +- .../tool-cordis/README.i18n.yaml | 4 +- .../self-modification/tool-cordis/README.md | 2 +- .../tool-cordis/README.zh.md | 2 +- .../typert/generator/src/cordis-catalog.ts | 145 +- .../tests/cordis-catalog-contract.spec.ts | 14 +- .../generator/tests/cordis-catalog.spec.ts | 24 +- scripts/cordis-core-api.spec.ts | 14 +- scripts/cordis-core-api.ts | 14 +- scripts/cordis-walk.ts | 53 +- scripts/gen-config-catalog.ts | 2 +- scripts/gen-cordis-catalog-record.spec.ts | 144 + scripts/gen-cordis-catalog.ts | 378 ++- scripts/gen-doc-graphs.ts | 2 +- scripts/gen-persistence-catalog.ts | 2 +- scripts/gen-tool-catalog.ts | 2 +- scripts/project-doc-site.spec.ts | 6 +- scripts/project-doc-site.ts | 6 + scripts/translation-pairing.manifest.json | 2 +- scripts/translation-pairing.spec.ts | 41 + scripts/translation-pairing.ts | 109 + scripts/type-equiv.manifest.json | 15 + scripts/verify-translation-pairing.ts | 22 + website/docs.ts | 66 +- 214 files changed, 10434 insertions(+), 4447 deletions(-) create mode 100644 .agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml create mode 100644 .agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md create mode 100644 .agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md rename docs/{cordis-catalog/core => cordis-api}/context.md (91%) rename docs/{cordis-catalog/core => cordis-api}/events.md (92%) rename docs/{cordis-catalog/core => cordis-api}/fiber.md (90%) create mode 100644 docs/cordis-api/inherited.md rename docs/{cordis-catalog/core => cordis-api}/registry.md (95%) rename docs/{cordis-catalog/core => cordis-api}/service.md (82%) delete mode 100644 docs/cordis-catalog/events.md delete mode 100644 docs/cordis-catalog/services.md create mode 100644 docs/subsystems/session-projection.i18n.yaml create mode 100644 docs/subsystems/session-projection.md create mode 100644 docs/subsystems/session-projection.zh.md create mode 100644 scripts/gen-cordis-catalog-record.spec.ts diff --git a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.i18n.yaml index efeb9d72f3..336d9669ed 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md -2026-07-05-subagent-provider-lifecycle-events.md: afd45027e8b56cbf1d17e6dec749d8602c81124d -2026-07-05-subagent-provider-lifecycle-events.zh.md: 01e12946c74fc4fb33c96e047d2030e99b692f47 +2026-07-05-subagent-provider-lifecycle-events.md: 503c0d638785e3a4944c8903b8d7d469c68b1881 +2026-07-05-subagent-provider-lifecycle-events.zh.md: e7d1d8c398a6d77efb2dbccc365e0040f38f2d33 diff --git a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md index afd45027e8..503c0d6387 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md +++ b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md @@ -31,6 +31,6 @@ The events also complete the seam's vocabulary: `ctx.subagents` is a named regis ## Consequences - Consumers deriving state from a named provider react to `subagent/provider-added`/`-removed` instead of reading the registry at `apply` time; `dsh-tool-subagent` is the reference implementation. -- **Addition fails loud; removal is contained per listener.** An addition listener may unwind registration. Removal runs during disposal, so one throwing listener is logged without starving later mirrors or disrupting teardown. `start()` still resolves the provider by name for every run, preventing stale tools from calling a removed backend. See the [events catalog](../../../../docs/cordis-catalog/events.md) and [producer/consumer map](../../../../docs/event-producer-consumer.md). +- **Addition fails loud; removal is contained per listener.** An addition listener may unwind registration. Removal runs during disposal, so one throwing listener is logged without starving later mirrors or disrupting teardown. `start()` still resolves the provider by name for every run, preventing stale tools from calling a removed backend. See the [events catalog](../../../../docs/subsystems/subagent.md#cordis-surface) and [producer/consumer map](../../../../docs/event-producer-consumer.md). - **A window where the tool is absent.** Between backend disposal and re-registration (an HMR reload), the model sees no subagent tool. This is the honest state — the alternative is a tool that dispatches into nothing — and the tool registry's `tools/change` emit keeps prompt assembly current. - **Two waiting fibers sharing a `toolName` is an invalid config caught late.** If two loads of `dsh-tool-subagent` name different providers but the same `toolName`, both wait, and whichever provider arrives first registers; the second registration throws only when ITS provider arrives. `TODO(subagent-dup-toolname)` in the plugin records this blast radius; the tool registry's duplicate-name rejection remains the backstop. diff --git a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md index 01e12946c7..e7d1d8c398 100644 --- a/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.zh.md @@ -31,6 +31,6 @@ Status: implemented ## 后果 - 从命名提供方派生状态的消费方响应 `subagent/provider-added`/`-removed` 事件,而非在 `apply` 时读取注册表;`dsh-tool-subagent` 是参考实现。 -- **添加时大声失败;移除时按监听器隔离。** 添加监听器可以回滚注册。移除在 disposal 期间运行,因此单个监听器抛异常只会被记录到日志中,不会阻止后续镜像运行或干扰拆解流程。`start()` 仍在每次运行时按名称解析提供方,防止陈旧工具调用已移除的后端。见[事件目录](../../../../docs/cordis-catalog/events.md)与[生产者/消费方映射](../../../../docs/event-producer-consumer.md)。 +- **添加时大声失败;移除时按监听器隔离。** 添加监听器可以回滚注册。移除在 disposal 期间运行,因此单个监听器抛异常只会被记录日志,不会饿死后续镜像或干扰拆解流程。`start()` 仍在每次运行时按名称解析提供方,防止陈旧工具调用已移除的后端。见[事件目录](../../../../docs/subsystems/subagent.md#cordis-surface)与[生产者/消费方映射](../../../../docs/event-producer-consumer.md)。 - **工具不存在的窗口期。** 在后端 disposal 与重新注册之间(HMR 重载期间),模型看不到 subagent 工具。这是诚实的状态——替代方案是一个向空处分发的工具——工具注册表发出的 `tools/change` 事件会使提示词组装保持最新状态。 - **两个等待中的 fiber 共享同一 `toolName` 是无效配置,被延迟捕获。** 如果两个 `dsh-tool-subagent` 加载实例分别指定了不同的提供方但相同的 `toolName`,两者都会等待,先到达的提供方先注册;第二次注册仅在其提供方到达时才抛异常。插件中的 `TODO(subagent-dup-toolname)` 记录了这一影响范围;工具注册表的重名拒绝机制仍是最终防线。 diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml index cdee6ab259..215f612f5e 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md -2026-07-08-agent-scope-contexts.md: 5e09bdbcae1e57e6b65eb7d1720a6e7a7f758a9f -2026-07-08-agent-scope-contexts.zh.md: 4714045f28e0386a3a53b53437d063462e75a9f1 +2026-07-08-agent-scope-contexts.md: eb3f6f247bac1a1d81aa2644132c7b9cc04d602c +2026-07-08-agent-scope-contexts.zh.md: 673dd8578bf545b1f14f3b7e7b89874d804e4ae9 diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md index 5e09bdbcae..eb3f6f247b 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md +++ b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md @@ -104,7 +104,7 @@ An event about Agent A normally reaches unscoped listeners and A-scoped listener At the Cordis level, `Scoped` is an opaque routing receiver. It carries the filter used to choose listeners but is not the domain object. Event signatures therefore keep the real `Agent`, tool execution, approval request, or other subject as an explicit argument that listeners can inspect. -A listener registered with `{ global: true }` deliberately bypasses contextual audience filtering while its cleanup still follows the registering context. Registry-membership notifications remain unfiltered because they describe shared registry state rather than one agent's operation. The generated [event catalog](../../../../docs/cordis-catalog/events.md) is the exhaustive event reference. +A listener registered with `{ global: true }` deliberately bypasses contextual audience filtering while its cleanup still follows the registering context. Registry-membership notifications remain unfiltered because they describe shared registry state rather than one agent's operation. The exhaustive event reference is the set of generated `cordis-surface` regions across the [subsystem pages](../../../../docs/subsystems/core.md) — each event scope on its owning page (`agent/*` and `agent-loop/*` on core.md itself). ### Creation publishes last and disposal revokes last diff --git a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md index 4714045f28..673dd8578b 100644 --- a/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md +++ b/.agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.zh.md @@ -104,7 +104,7 @@ setup 接收一个完整的受信 Cordis 上下文,因此可以组合普通插 在 Cordis 层面,`Scoped` 是一个不透明的路由接收器。它携带用于选择监听器的过滤器,但本身不是领域对象。因此事件签名将真实的 `Agent`、工具执行、审批请求或其他主体作为显式参数保留,供监听器检查。 -以 `{ global: true }` 注册的监听器有意绕过上下文受众过滤,但其清理仍跟随注册上下文。注册表成员变更通知保持不过滤,因为它们描述的是共享注册表状态而非某个 agent 的操作。生成的[事件目录](../../../../docs/cordis-catalog/events.md)是详尽的事件参考。 +以 `{ global: true }` 注册的监听器有意绕过上下文受众过滤,但其清理仍跟随注册上下文。注册表成员变更通知保持不过滤,因为它们描述的是共享注册表状态而非某个 agent 的操作。详尽的事件参考是各[子系统页面](../../../../docs/subsystems/core.md)上生成的 `cordis-surface` 区块的集合——每个事件作用域在其所属页面上(`agent/*` 与 `agent-loop/*` 在 core.md 本页)。 ### 创建最后发布,dispose 最后撤销 diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml index 6fff14bc85..e0f79bacb0 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-07-05-skill-system.md -2026-07-05-skill-system.md: bb7c03a64a3d0d0f700e21bc96633f1c3ea181b9 -2026-07-05-skill-system.zh.md: 8e26e52d3366505a958dd4b19461edf8bf740b00 +2026-07-05-skill-system.md: 5961c535a1397c487b0255f8a27d60929c2eeace +2026-07-05-skill-system.zh.md: 05bd880ecbd46f2b472eb2cc5442f11c07f89b4e diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.md index bb7c03a64a..5961c535a1 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.md @@ -28,7 +28,7 @@ Local skill filesystem I/O goes through `ctx.fs` when a filesystem service is lo The registry's `list()` returns every winning summary, while model and user consumers apply the invocation predicates owned by the [independent invocation-policy decision](2026-07-28-skill-invocation-policy.md). The `skill({ name })` tool loads one model-invocable skill for the current agent cwd and returns a tool result containing ``, ``, and ``. `resourceBase` supplies a directory, URL, or opaque provider-managed base for explicitly referenced scripts, references, and assets; resources load only as needed, without directory enumeration. An unresolved name reports that the skill is unknown or no longer available; invalid names and skills with `invocation.modelInvocable: false` retain distinct tool errors. The tool result is the model-visible disclosure path. -The data structures and catalog/tool contract are documented in [skills.md](../../../../docs/subsystems/skills.md), with service signatures in the generated [services catalog](../../../../docs/cordis-catalog/services.md). +The data structures and catalog/tool contract are documented in [skills.md](../../../../docs/subsystems/skills.md), with service signatures in the generated [services catalog](../../../../docs/subsystems/skills.md#cordis-surface). ## Alternatives considered diff --git a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md index 8e26e52d33..05bd880ecb 100644 --- a/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md +++ b/.agents/notes/implemented/feature/2026-07-05-skill-system.zh.md @@ -28,7 +28,7 @@ DeepSeek Harness 使用同一原语,使项目特定的评审、插件编写和 注册表的 `list()` 返回全部胜出摘要,而模型与用户消费方应用[独立调用策略决策](2026-07-28-skill-invocation-policy.md)定义的调用判定。`skill({ name })` 工具为当前 agent cwd 加载一个模型可调用的 skill,返回包含 ``、`` 和 `` 的工具结果。`resourceBase` 提供一个目录、URL 或不透明的提供方管理的基路径,用于显式引用的脚本、参考资料和资产;资源仅按需加载,不进行目录枚举。无法解析的名称报告该 skill 未知或不再可用;无效名称和 `invocation.modelInvocable` 为 `false` 的 skill 保留不同的工具错误。工具结果是面向模型的可见披露路径。 -数据结构与目录/工具契约记录在 [skills.md](../../../../docs/subsystems/skills.md) 中,服务签名见生成的[服务目录](../../../../docs/cordis-catalog/services.md)。 +数据结构与目录/工具契约记录在 [skills.md](../../../../docs/subsystems/skills.md) 中,服务签名见生成的[服务目录](../../../../docs/subsystems/skills.md#cordis-surface)。 ## 曾考虑的替代方案 diff --git a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml new file mode 100644 index 0000000000..102b0ce5fe --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md +2026-07-28-per-subsystem-cordis-surface-regions.md: ef65bfc4c7dadd7dafe1a38f41656e6ecc61ea50 +2026-07-28-per-subsystem-cordis-surface-regions.zh.md: 1df18d7260800b89c95a9d4aeb0301adb0ee56f6 diff --git a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md new file mode 100644 index 0000000000..ef65bfc4c7 --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.md @@ -0,0 +1,36 @@ +# Agent Note: Per-subsystem generated cordis-surface regions + +Status: implemented + +English | [中文](2026-07-28-per-subsystem-cordis-surface-regions.zh.md) + +## Problem + +One subsystem's documentation was split across three homes: its hand-written subsystems page (introduction, data structures, verbs), its `ctx.` slice of the flat generated `docs/cordis-catalog/services.md`, and its event scope's slice of the flat `docs/cordis-catalog/events.md`. A reader of bash.md had to open two more documents to see the service interface and events the page was describing, and nothing tied the three views together beyond hand-maintained links. The flat catalogs also sat outside the bilingual corpus (excluded from pairing because generated output is English-only), so the reference surface had no Chinese route at all. + +The [generated-catalog decision](../../archived/process/2026-06-20-generated-cordis-catalog.md) — generate from source, `@mode` tags cross-checked, fail-closed type-link coverage, the `ts cordis-catalog` fence — is not in question; what changed is WHERE the generated output lands. + +## Decision + +`gen-cordis-catalog.ts` injects each subsystem's service and event reference INTO its own page, between `` / `` markers, and the flat services/events catalogs are deleted. One page per subsystem now carries introduction, data structures, and the generated wiring surface. + +- **Curated fail-loud partition.** `SERVICE_PAGE` maps every discovered `ctx.` to exactly one page; `EVENT_SCOPE_PAGE` maps every event scope. The generator hard-errors in both directions — an unmapped discovered service/scope, and a mapped key/scope the walk no longer discovers — so the partition cannot drift from the source surface. An independent scan of EVERY `declare module 'cordis'` Context merge backstops the rendering walk's blind spot (it only sees a root `index.ts` with a same-named service class): a declared key the walk cannot render must carry a named `SERVICE_WALK_EXEMPTIONS` reason (today: the `ctx.agent` DX accessor, plus the interface-typed or non-index-declared lsp/apiProxy/appShell/tuiPrompt/tuiResumeHost), and stale exemptions hard-error; a `TODO(cordis-catalog-interface-services)` marks teaching the walk to render them. +- **Byte-identical regions across the pair.** The generator writes the SAME English region bytes into `foo.md` and `foo.zh.md`, extending the existing rule that verbatim code fences match across a pair. `verify-translation-pairing` gained a dedicated region-identity check (`partitionGeneratedRegions` in `translation-pairing.ts` owns the marker grammar) that names a divergent or malformed region precisely; the whole-document structural signature still covers the region content a second time. +- **Guarded pair auto-record.** A regeneration that changes region bytes would leave every touched pair out-of-sync, so the generator re-records a pair's `.i18n.yaml` itself — but ONLY when the write is region-confined: both sides' recorded blob hashes must match the pre-write bytes, and the region-STRIPPED content must be unchanged on both sides. Human-prose drift leaves the record stale so the pairing gate still forces the normal translation flow; a brand-new pair is never auto-recorded (the author's reviewed `--write` owns that). This keeps `.i18n.yaml` as plain `git hash-object` values — no stripped-hash semantics change. +- **The inherited tier moved, not died.** The vendor `ctx` members and `internal/*`/loader/hmr/timer events render to `docs/cordis-api/inherited.md`, next to the relocated Cordis core API pages (`docs/cordis-catalog/core/` → `docs/cordis-api/`). Framework surface lives under a framework home; the harness pages stay repository-owned vocabulary. +- **In-page links.** Signature `Types:` lines link sibling pages (`core.md`, `bash.md`); a type whose primary page is the rendering page is dropped from the line instead of self-linking. Pages reference their own region with `#cordis-surface` or a `#ctx--` anchor — every generated heading is preceded by an explicit `` carrying the GitHub slug (the historical flat-catalog anchor), so the fragments resolve identically on GitHub and the VitePress site, whose own slugger treats the punctuation-heavy headings differently. + +## Alternatives considered + +- **Keep the flat catalogs alongside the regions, both generated** — rejected: every JSDoc edit would produce double diff noise, and the scattering (one subsystem, three documents) this change exists to remove would survive. +- **Generator-owned whole pages with hand-written intros in fragment files** — rejected: the narrative prose is the majority of every existing page and belongs in the reviewed document itself; markers cost one grammar rule and keep authors editing the real file. +- **Localized regions (generator emits Chinese too)** — deferred, same status as the i18n README's long-standing note for the remaining generated docs: teaching the generator zh output means translating source JSDoc, which is machinery this change does not need. English regions inside zh pages match the existing status quo of English JSDoc inside verbatim fences. +- **Hashing region-stripped content in `.i18n.yaml`** — rejected: the record would stop being `git hash-object` of the file, breaking the recover-last-confirmed-text property and every consumer that recomputes hashes. + +## Consequences + +- A subsystem's whole story is one page: `docs/subsystems/.md` (and its pair) carries introduction, data structures/verbs, and the generated service/event surface; `docs/cordis-catalog/` no longer exists. +- A new service or event scope cannot ship undocumented or unmapped: the generator fails until `SERVICE_PAGE`/`EVENT_SCOPE_PAGE` names its owning page, and the page must already exist with markers in both language sides. +- Regeneration after a source-JSDoc change touches the affected pages in both languages plus (when region-confined) their pair records — a mechanical, reviewable diff; prose edits keep demanding the translation flow because the auto-record guard refuses them. +- The website's subsystem nav lists every page (38 routes per locale: 35 translated pairs plus the three still-English-mirrored goal/pty/commands), replacing the two flat catalog nav entries; the Cordis API section gained `inherited.md`. +- `packages/typert/generator/tests/cordis-catalog-contract.spec.ts` pins the region renderer (`renderPageRegion`), the same-page link-drop rule, and the fail-loud JSDoc/type-link validation; `scripts/translation-pairing.spec.ts` pins the marker grammar and blob-hash primitive; `scripts/gen-cordis-catalog-record.spec.ts` proves the auto-record guard refuses every invalid state (stale record, malformed or renamed-key sidecar, extra entries, prose drift, missing record, missing snapshot). diff --git a/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md new file mode 100644 index 0000000000..1df18d7260 --- /dev/null +++ b/.agents/notes/implemented/process/2026-07-28-per-subsystem-cordis-surface-regions.zh.md @@ -0,0 +1,36 @@ +# Agent Note: 按子系统生成的 cordis-surface 区块 + +Status: implemented + +[English](2026-07-28-per-subsystem-cordis-surface-regions.md) | 中文 + +## 问题 + +一个子系统的文档过去分散在三个归属:手写的 subsystems 页面(介绍、数据结构、动词)、平铺生成的 `docs/cordis-catalog/services.md` 中属于它的 `ctx.` 切片,以及平铺的 `docs/cordis-catalog/events.md` 中属于其事件作用域的切片。bash.md 的读者必须再打开两份文档,才能看到该页面正在描述的服务接口与事件;除了手工维护的链接,没有任何机制把这三个视图联系在一起。平铺目录还游离在双语语料之外(生成输出只有英文,故被排除在配对之外),于是这个参考面完全没有中文入口。 + +[生成式目录决策](../../archived/process/2026-06-20-generated-cordis-catalog.md)本身(从源码生成、`@mode` 标签交叉校验、失败关闭的类型链接覆盖、`ts cordis-catalog` 围栏)不在质疑之列;改变的只是生成输出「落在哪里」。 + +## 决策 + +`gen-cordis-catalog.ts` 把每个子系统的服务与事件参考注入到该子系统自己的页面内部,置于 `` / `` 标记之间;平铺的 services/events 目录随之删除。现在每个子系统由一个页面同时承载介绍、数据结构和生成的接线表面。 + +- **人工维护、响亮失败的划分。** `SERVICE_PAGE` 把发现的每个 `ctx.` 映射到恰好一个页面;`EVENT_SCOPE_PAGE` 映射每个事件作用域。生成器在两个方向上都会直接报错(既有被发现却未映射的服务或作用域,也有已映射但遍历不再发现的键或作用域),因此划分不可能相对源码表面发生漂移。一个独立扫描会读取每一处 `declare module 'cordis'` 的 Context 合并,为渲染遍历的盲区(它只看得到根 `index.ts` 中同名服务类)兜底:遍历渲染不了的已声明键必须在 `SERVICE_WALK_EXEMPTIONS` 中带着点名理由(目前是 `ctx.agent` 这一 DX 访问器字段,加上接口类型或不在 index 声明的 lsp/apiProxy/appShell/tuiPrompt/tuiResumeHost),陈旧豁免同样直接报错;教会遍历渲染它们的后续工作由 `TODO(cordis-catalog-interface-services)` 标记。 +- **区块在配对两侧按字节一致。** 生成器把同一份英文区块字节写入 `foo.md` 和 `foo.zh.md`,是对「围栏代码块在配对两侧逐字节一致」这一既有规则的延伸。`verify-translation-pairing` 新增了专门的区块一致性检查(标记语法归 `translation-pairing.ts` 中的 `partitionGeneratedRegions` 所有),能精确点名出现分歧或格式错误的区块;整篇文档的结构签名仍会把区块内容再覆盖一遍。 +- **带防护的配对自动记录。** 一次改变区块字节的重新生成会让每个被触及的配对失去同步,因此生成器会自行重新记录配对的 `.i18n.yaml`,但仅限本次写入完全限定在区块内的情况:两侧记录的 blob hash 必须与写入前的字节相符,且两侧剥离区块后的内容必须没有变化。人工行文若有漂移,记录就保持陈旧,配对门禁因此仍会强制走正常翻译流程;全新的配对绝不自动记录(那归作者经评审的 `--write` 所有)。这样 `.i18n.yaml` 保持为纯粹的 `git hash-object` 值:不引入任何「剥离后 hash」的语义变化。 +- **继承层搬了家,而非消亡。** vendor 的 `ctx` 成员与 `internal/*`/loader/hmr/timer 事件渲染到 `docs/cordis-api/inherited.md`,紧邻迁移后的 Cordis 核心 API 页面(`docs/cordis-catalog/core/` → `docs/cordis-api/`)。框架表面落在框架自己的归属之下;harness 页面仍是仓库自有的词汇。 +- **页内链接。** 签名的 `Types:` 行链接到兄弟页面(`core.md`、`bash.md`);若某个类型的主要页面就是正在渲染的页面,该类型会从该行去掉,而不是链接到自身。页面用 `#cordis-surface` 或 `#ctx--` 锚点引用自己的区块:每个生成标题前都有一个显式 ``,携带 GitHub slug(即平铺目录时期的历史锚点),因此这些片段在 GitHub 与 VitePress 站点上解析一致——后者自带的 slugger 对含大量标点的标题会得出不同结果。 + +## 曾考虑的替代方案 + +- **平铺目录与区块并存、两者都生成**:否决。每次 JSDoc 编辑都会产生双份 diff 噪音,而本次变更本要消除的分散状况(一个子系统、三份文档)也将延续。 +- **整页归生成器所有、手写介绍放进片段文件**:否决。叙述性行文占每个现有页面的大部分,应当留在被评审的文档本身;标记只花一条语法规则的成本,还让作者继续编辑真实文件。 +- **本地化区块(生成器同时输出中文)**:推迟,与 i18n README 中针对其余生成文档的长期备注同属一个状态:教会生成器输出中文意味着要翻译源码 JSDoc,而那是本次变更并不需要的机制。zh 页面里的英文区块,与「英文 JSDoc 出现在逐字节一致的围栏代码块内」这一既有现状相符。 +- **在 `.i18n.yaml` 中对剥离区块后的内容做 hash**:否决。记录将不再是文件的 `git hash-object`,这会破坏「还原上次确认文本」的性质,也会破坏每个自行重算 hash 的消费方。 + +## 后果 + +- 一个子系统的完整故事就在一个页面上:`docs/subsystems/.md`(及其配对文件)承载介绍、数据结构/动词,以及生成的服务/事件表面;`docs/cordis-catalog/` 不复存在。 +- 新的服务或事件作用域无法在未记录、未映射的状态下落地:在 `SERVICE_PAGE`/`EVENT_SCOPE_PAGE` 点名其所属页面之前,生成器一直失败,而且该页面必须已经存在,并在两个语言侧都带有标记。 +- 源码 JSDoc 变更后的重新生成会触及两种语言的受影响页面,外加(当写入限定在区块内时)它们的配对记录:一份机械、可评审的 diff。行文编辑仍然要走翻译流程,因为自动记录防护会拒绝它们。 +- 网站的子系统导航列出每个页面(每个 locale 38 条路由:35 个已翻译配对,加上仍为英文镜像的 goal/pty/commands 三页),取代两个平铺目录导航项;Cordis API 一节新增 `inherited.md`。 +- `packages/typert/generator/tests/cordis-catalog-contract.spec.ts` 固定区块渲染器(`renderPageRegion`)、同页链接去除规则,以及响亮失败的 JSDoc 与类型链接校验;`scripts/translation-pairing.spec.ts` 固定标记语法与 blob hash 原语;`scripts/gen-cordis-catalog-record.spec.ts` 证明自动重录守卫拒绝每一种非法状态(陈旧记录、格式错误或键被改名的伴随记录、多余条目、行文漂移、记录缺失、快照缺失)。 diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 488a7a1dd6..5194544f95 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -28,7 +28,7 @@ Each fact has one home: the tier whose job it is. Elsewhere, link to that home. | [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history | | Package README | Per-package config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc or catalog restatement, other packages' concerns | | [development.md](development.md) | Contributor onboarding: setup, daily workflow, and CI shape at summary level | Runtime rationale (→ Agent Notes), drifting gate inventories | -| Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [Cordis core API](cordis-catalog/core/context.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | +| Generated reference: the per-page `cordis-surface` regions in [subsystems/](subsystems/core.md), the [Cordis core API + inherited tier](cordis-api/context.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind (regions included) | | Skills (`.agents/skills/`) | Reusable workflows and specialized decision standards | Product and runtime contracts (→ docs or source) | ## Writing rules diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 15a6ba3fb5..2436ff9027 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: 7baf550128fe83528ce12bf2f9b40cfc8f78e322 -architecture.zh.md: 076edd0549fb7f648a7a1b06d7027a1e6eca735f +architecture.md: f593ee5c54e5bd2cbdaa5da9f2de54b7b08ec14b +architecture.zh.md: 5780d54aad6085ef7811dfa94377a4c2478bfb99 diff --git a/docs/architecture.md b/docs/architecture.md index 7baf550128..f593ee5c54 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -53,7 +53,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services, ## Event -Events are the service extension API ([catalog](cordis-catalog/events.md), [producer/consumer map](event-producer-consumer.md)). +Events are the service extension API ([subsystems](subsystems/core.md), [producer/consumer map](event-producer-consumer.md)). ### Event Domains diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 076edd0549..5780d54aad 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -53,7 +53,7 @@ ## 事件 -事件就是服务的扩展 API([目录](cordis-catalog/events.md)、[生产方与消费方映射](event-producer-consumer.md))。 +事件就是服务的扩展 API([子系统](subsystems/core.md)、[生产方与消费方映射](event-producer-consumer.md))。 ### 事件域 diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 850f4731e2..eb4472d629 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -3,7 +3,7 @@ # Plugin Config Catalog -Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin's full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference. +Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin's full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the generated `cordis-surface` region on each [subsystem page](subsystems/core.md), the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference. This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verified fresh by `pnpm run verify-config-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks use a `ts config-catalog` fence (skipped by doc-typecheck, since a lone declaration referencing imports is not standalone-compilable). The generator also cross-checks the runtime schemastery schema against the pasted declaration — every schema-validated key, nested keys included, must be locatable on the declared config type — so the paste cannot hide a loader-accepted field. diff --git a/docs/cordis-catalog/core/context.md b/docs/cordis-api/context.md similarity index 91% rename from docs/cordis-catalog/core/context.md rename to docs/cordis-api/context.md index c0c1690c8f..320b23de1c 100644 --- a/docs/cordis-catalog/core/context.md +++ b/docs/cordis-api/context.md @@ -9,7 +9,7 @@ Root and child dependency containers for Cordis plugins. A context is a proxy: normal property reads go through the service resolver, while `extend()`, `isolate()`, and `intercept()` create scoped child contexts without mutating their parent. -[Source](../../../vendor/cordis/src/context.ts#L42) +[Source](../../vendor/cordis/src/context.ts#L42) ### ctx.extend(meta?) @@ -34,7 +34,7 @@ The child prototypally inherits every property of this context; own properties o **Returns** a child context inheriting from this one. -[Source](../../../vendor/cordis/src/context.ts#L99) +[Source](../../vendor/cordis/src/context.ts#L99) ### ctx.isolate(name, label?) @@ -63,7 +63,7 @@ Below the returned context, reads and writes of the service `name` resolve again **Returns** a child context whose `name` service resolves in the new scope. -[Source](../../../vendor/cordis/src/context.ts#L121) +[Source](../../vendor/cordis/src/context.ts#L121) ### ctx.intercept(name, config) @@ -93,7 +93,7 @@ Plugins loaded under the returned context see `config` merged into the service's **Returns** a child context carrying the additional intercept entry. -[Source](../../../vendor/cordis/src/context.ts#L139) +[Source](../../vendor/cordis/src/context.ts#L139) ### ctx.root @@ -104,7 +104,7 @@ root: this The root context of the application (every child context shares it). @experimental -[Source](../../../vendor/cordis/src/context.ts#L22) +[Source](../../vendor/cordis/src/context.ts#L22) ### ctx.baseUrl @@ -115,7 +115,7 @@ baseUrl?: string Base URL used to resolve relative plugin/module specifiers, if the runtime sets one. -[Source](../../../vendor/cordis/src/context.ts#L24) +[Source](../../vendor/cordis/src/context.ts#L24) ### ctx.events @@ -126,7 +126,7 @@ events: EventsService The event bus. Its methods are also mixed onto `ctx` (`ctx.on`, `ctx.emit`, ...). -[Source](../../../vendor/cordis/src/context.ts#L26) +[Source](../../vendor/cordis/src/context.ts#L26) ### ctx.logger @@ -137,7 +137,7 @@ logger: LoggerService The logging service. Call `ctx.logger(name)` for a named logger. -[Source](../../../vendor/cordis/src/context.ts#L28) +[Source](../../vendor/cordis/src/context.ts#L28) ### ctx.reflect @@ -148,7 +148,7 @@ reflect: ReflectService The reflection layer backing the context proxy (`ctx.get`, `ctx.provide`, ...). -[Source](../../../vendor/cordis/src/context.ts#L30) +[Source](../../vendor/cordis/src/context.ts#L30) ### ctx.registry @@ -159,7 +159,7 @@ registry: RegistryService The plugin registry. Its methods are mixed onto `ctx` (`ctx.plugin`, `ctx.inject`). -[Source](../../../vendor/cordis/src/context.ts#L32) +[Source](../../vendor/cordis/src/context.ts#L32) ## Static members @@ -172,7 +172,7 @@ static readonly effect: unique symbol Symbol key under which a disposer exposes its EffectMeta diagnostics tree. -[Source](../../../vendor/cordis/src/context.ts#L44) +[Source](../../vendor/cordis/src/context.ts#L44) ### Context.filter @@ -183,7 +183,7 @@ static readonly filter: unique symbol Symbol key for a context's listener filter, consulted on every event dispatch. -[Source](../../../vendor/cordis/src/context.ts#L46) +[Source](../../vendor/cordis/src/context.ts#L46) ### Context.isolate @@ -194,7 +194,7 @@ static readonly isolate: unique symbol Symbol key of the isolation map (see the `Context[symbols.isolate]` property). -[Source](../../../vendor/cordis/src/context.ts#L48) +[Source](../../vendor/cordis/src/context.ts#L48) ### Context.intercept @@ -205,7 +205,7 @@ static readonly intercept: unique symbol Symbol key of the intercept map (see the `Context[symbols.intercept]` property). -[Source](../../../vendor/cordis/src/context.ts#L50) +[Source](../../vendor/cordis/src/context.ts#L50) ### Context.is(value) @@ -230,7 +230,7 @@ Works across realms and across multiple copies of cordis, because the brand is k **Returns** `true` if `value` is a Cordis context, narrowing its type. -[Source](../../../vendor/cordis/src/context.ts#L61) +[Source](../../vendor/cordis/src/context.ts#L61) ## Service store and mixins @@ -256,7 +256,7 @@ Read a service from the store without the inject requirement. **Returns** the service value, or `undefined` when not (yet) provided. -[Source](../../../vendor/cordis/src/reflect.ts#L17) +[Source](../../vendor/cordis/src/reflect.ts#L17) ### ctx.set(name, value) @@ -281,7 +281,7 @@ Only the fiber that provided the service may set it; setting an unprovided name - `name` — the service name. - `value` — the new service value. -[Source](../../../vendor/cordis/src/reflect.ts#L29) +[Source](../../vendor/cordis/src/reflect.ts#L29) ### ctx.provide(name, value) @@ -311,7 +311,7 @@ The service becomes visible to dependents in the same isolation scope once the f **Returns** a disposer that unregisters the service. -[Source](../../../vendor/cordis/src/reflect.ts#L44) +[Source](../../vendor/cordis/src/reflect.ts#L44) ### ctx.accessor(name, options) @@ -335,7 +335,7 @@ The accessor is removed when the current fiber unloads. Throws if the name is al - `name` — the context property name. - `options` — the `get` hook and optional `set` hook. -[Source](../../../vendor/cordis/src/reflect.ts#L56) +[Source](../../vendor/cordis/src/reflect.ts#L56) ### ctx.mixin(name, mixins) @@ -361,4 +361,4 @@ Each mixed-in key becomes an accessor that forwards to the service (binding meth - `name` — the context property holding the source service. - `mixins` — keys to forward, or a source-key → ctx-key map. -[Source](../../../vendor/cordis/src/reflect.ts#L67) +[Source](../../vendor/cordis/src/reflect.ts#L67) diff --git a/docs/cordis-catalog/core/events.md b/docs/cordis-api/events.md similarity index 92% rename from docs/cordis-catalog/core/events.md rename to docs/cordis-api/events.md index 68cd109e8e..8b8eb2358e 100644 --- a/docs/cordis-catalog/core/events.md +++ b/docs/cordis-api/events.md @@ -3,7 +3,7 @@ # Events -The event-dispatch API mixed into every context. Harness event declarations and their dispatch modes are generated separately in the [Cordis events catalog](../events.md). +The event-dispatch API mixed into every context. Harness event declarations and their dispatch modes are generated into each owning [subsystem page](../subsystems/core.md). ### ctx.parallel(name, ...args) @@ -26,7 +26,7 @@ Dispatch an event, running all listeners concurrently. **Returns** a promise resolving once every listener has settled. -[Source](../../../vendor/cordis/src/events.ts#L44) +[Source](../../vendor/cordis/src/events.ts#L44) ### ctx.emit(name, ...args) @@ -46,7 +46,7 @@ Dispatch an event synchronously, ignoring listener return values. - `name` — the event name. - `args` — arguments passed to every listener. -[Source](../../../vendor/cordis/src/events.ts#L53) +[Source](../../vendor/cordis/src/events.ts#L53) ### ctx.serial(name, ...args) @@ -69,7 +69,7 @@ Dispatch an event, awaiting listeners in order until one bails. **Returns** the first bail value (non-null, non-false, non-undefined), if any. -[Source](../../../vendor/cordis/src/events.ts#L63) +[Source](../../vendor/cordis/src/events.ts#L63) ### ctx.bail(name, ...args) @@ -92,7 +92,7 @@ Dispatch an event, calling listeners in order until one bails. **Returns** the first bail value (non-null, non-false, non-undefined), if any. -[Source](../../../vendor/cordis/src/events.ts#L73) +[Source](../../vendor/cordis/src/events.ts#L73) ### ctx.waterfall(name, ...args) @@ -120,7 +120,7 @@ Each listener wraps the rest of the chain: calling `next()` invokes the next lis **Returns** the outermost listener's return value. -[Source](../../../vendor/cordis/src/events.ts#L86) +[Source](../../vendor/cordis/src/events.ts#L86) ### ctx.on(name, listener, options?) @@ -144,7 +144,7 @@ Register an event listener owned by the current fiber. **Returns** a disposer removing the listener; `true` if it was still registered. -[Source](../../../vendor/cordis/src/events.ts#L97) +[Source](../../vendor/cordis/src/events.ts#L97) ### ctx.once(name, listener, options?) @@ -168,7 +168,7 @@ Same as `on()`, but the listener disposes itself after its first call. **Returns** a disposer removing the listener; `true` if it was still registered. -[Source](../../../vendor/cordis/src/events.ts#L106) +[Source](../../vendor/cordis/src/events.ts#L106) ## EventOptions @@ -184,7 +184,7 @@ interface EventOptions { } ``` -[Source](../../../vendor/cordis/src/events.ts#L112) +[Source](../../vendor/cordis/src/events.ts#L112) ## DispatchMode @@ -204,4 +204,4 @@ Event dispatch strategy used by the event service. type DispatchMode = 'emit' | 'parallel' | 'serial' | 'bail' | 'waterfall' ``` -[Source](../../../vendor/cordis/src/events.ts#L32) +[Source](../../vendor/cordis/src/events.ts#L32) diff --git a/docs/cordis-catalog/core/fiber.md b/docs/cordis-api/fiber.md similarity index 90% rename from docs/cordis-catalog/core/fiber.md rename to docs/cordis-api/fiber.md index 35a991f789..36d2861ac6 100644 --- a/docs/cordis-catalog/core/fiber.md +++ b/docs/cordis-api/fiber.md @@ -34,7 +34,7 @@ Register a cleanup-aware effect on this fiber. **Returns** a disposer that tears the effect down and settles once done. -[Source](../../../vendor/cordis/src/fiber.ts#L420) +[Source](../../vendor/cordis/src/fiber.ts#L420) ### ctx.fiber @@ -45,7 +45,7 @@ fiber: Fiber The fiber (plugin runtime instance) that owns this context. -[Source](../../../vendor/cordis/src/fiber.ts#L12) +[Source](../../vendor/cordis/src/fiber.ts#L12) ## The Fiber class @@ -53,7 +53,7 @@ Runtime instance of one plugin application. A fiber tracks dependency state, validated config, lifecycle effects, and cleanup for the plugin context returned by `ctx.plugin()`. -[Source](../../../vendor/cordis/src/fiber.ts#L184) +[Source](../../vendor/cordis/src/fiber.ts#L184) ### fiber.uid @@ -64,7 +64,7 @@ public uid: number | null Unique id within the registry; 0 for the root fiber, `null` once disposed. -[Source](../../../vendor/cordis/src/fiber.ts#L186) +[Source](../../vendor/cordis/src/fiber.ts#L186) ### fiber.ctx @@ -75,7 +75,7 @@ public readonly ctx: Context The context this fiber's plugin runs in (extends the parent context). -[Source](../../../vendor/cordis/src/fiber.ts#L188) +[Source](../../vendor/cordis/src/fiber.ts#L188) ### fiber.config @@ -86,7 +86,7 @@ public config: any The validated plugin config (updated by `update()`). -[Source](../../../vendor/cordis/src/fiber.ts#L190) +[Source](../../vendor/cordis/src/fiber.ts#L190) ### fiber.state @@ -97,7 +97,7 @@ public state Current lifecycle state; transitions emit `internal/status`. -[Source](../../../vendor/cordis/src/fiber.ts#L192) +[Source](../../vendor/cordis/src/fiber.ts#L192) ### fiber.dispose @@ -108,7 +108,7 @@ public readonly dispose: () => Promise Dispose this fiber: unload the plugin, then settle once cleanup finished. -[Source](../../../vendor/cordis/src/fiber.ts#L194) +[Source](../../vendor/cordis/src/fiber.ts#L194) ### fiber.store @@ -119,7 +119,7 @@ public store: Dict | undefined Snapshot of required service implementations while loaded; `undefined` otherwise. -[Source](../../../vendor/cordis/src/fiber.ts#L196) +[Source](../../vendor/cordis/src/fiber.ts#L196) ### fiber.inertia @@ -130,7 +130,7 @@ public inertia: Promise | undefined The in-flight load/unload transition, if one is currently running. -[Source](../../../vendor/cordis/src/fiber.ts#L198) +[Source](../../vendor/cordis/src/fiber.ts#L198) ### fiber.name @@ -141,7 +141,7 @@ get name() The plugin's display name, inherited from the nearest named ancestor, else `'root'`. -[Source](../../../vendor/cordis/src/fiber.ts#L341) +[Source](../../vendor/cordis/src/fiber.ts#L341) ### fiber.assertActive() @@ -159,7 +159,7 @@ Throw if the fiber has already been disposed. **Returns** nothing when the fiber is still active. -[Source](../../../vendor/cordis/src/fiber.ts#L356) +[Source](../../vendor/cordis/src/fiber.ts#L356) ### fiber.effect(execute, label?) @@ -190,7 +190,7 @@ Register a cleanup-aware effect on this fiber. **Returns** a disposer that tears the effect down and settles once done. -[Source](../../../vendor/cordis/src/fiber.ts#L420) +[Source](../../vendor/cordis/src/fiber.ts#L420) ### fiber.getEffects() @@ -207,7 +207,7 @@ Return metadata for currently registered effects. **Returns** one `EffectMeta` tree per labeled live effect. -[Source](../../../vendor/cordis/src/fiber.ts#L573) +[Source](../../vendor/cordis/src/fiber.ts#L573) ### fiber.await() @@ -225,7 +225,7 @@ Wait for current lifecycle work and rethrow startup errors. **Returns** this fiber, once it has settled into a stable state. -[Source](../../../vendor/cordis/src/fiber.ts#L702) +[Source](../../vendor/cordis/src/fiber.ts#L702) ### fiber.restart() @@ -243,7 +243,7 @@ Dispose and immediately reload this plugin with its current config. **Returns** a promise resolving once the reload settled. -[Source](../../../vendor/cordis/src/fiber.ts#L716) +[Source](../../vendor/cordis/src/fiber.ts#L716) ### fiber.update(config, noSave?) @@ -271,7 +271,7 @@ Runs the `internal/update` waterfall first, so update hooks (and HMR) can veto o **Returns** the update waterfall result; the default restart returns a promise. -[Source](../../../vendor/cordis/src/fiber.ts#L734) +[Source](../../vendor/cordis/src/fiber.ts#L734) ## Effect @@ -292,7 +292,7 @@ type Effect = | AsyncEffect ``` -[Source](../../../vendor/cordis/src/fiber.ts#L83) +[Source](../../vendor/cordis/src/fiber.ts#L83) ## Disposable @@ -310,7 +310,7 @@ Disposers run in reverse registration order when the owning fiber unloads; they type Disposable = () => T ``` -[Source](../../../vendor/cordis/src/fiber.ts#L74) +[Source](../../vendor/cordis/src/fiber.ts#L74) ## EffectMeta @@ -326,7 +326,7 @@ interface EffectMeta { } ``` -[Source](../../../vendor/cordis/src/fiber.ts#L96) +[Source](../../vendor/cordis/src/fiber.ts#L96) ## CordisError @@ -352,7 +352,7 @@ namespace CordisError { } ``` -[Source](../../../vendor/cordis/src/fiber.ts#L157) +[Source](../../vendor/cordis/src/fiber.ts#L157) ## ValidationError @@ -372,4 +372,4 @@ class ValidationError extends TypeError { } ``` -[Source](../../../vendor/cordis/src/fiber.ts#L19) +[Source](../../vendor/cordis/src/fiber.ts#L19) diff --git a/docs/cordis-api/inherited.md b/docs/cordis-api/inherited.md new file mode 100644 index 0000000000..5582bdd1ac --- /dev/null +++ b/docs/cordis-api/inherited.md @@ -0,0 +1,39 @@ + + +# Inherited Cordis Surface + +The framework `ctx` members and events every plugin sees beyond the harness tier — pinned vendor source ([vendoring policy](../../vendor/README.md)), summarized tersely so the harness pages stay focused on repository-owned vocabulary. Detailed Context, Fiber, Registry, and Service APIs are generated in [context.md](context.md), [fiber.md](fiber.md), [registry.md](registry.md), and [service.md](service.md); the event-dispatch methods in [events.md](events.md). + +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. + +## Inherited `ctx` members (cordis core + loader/hmr/timer) + +- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) +- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) +- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:164`](../../vendor/cordis/src/registry.ts)) +- `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) +- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) +- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts)) +- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) +- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) +- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) +- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts)) + +## Inherited events (cordis core + loader/hmr/timer) + +- `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:328`](../../vendor/cordis/src/events.ts)) +- `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:330`](../../vendor/cordis/src/events.ts)) +- `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:332`](../../vendor/cordis/src/events.ts)) +- `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:334`](../../vendor/cordis/src/events.ts)) +- `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:336`](../../vendor/cordis/src/events.ts)) +- `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:338`](../../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)) +- `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:21`](../../vendor/hmr/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/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts)) +- `loader/partial-dispose` — An entry is being partially disposed on reload. ([`vendor/loader/src/index.ts:26`](../../vendor/loader/src/index.ts)) +- `loader/patch-context` — A context is being patched during a reload. ([`vendor/loader/src/index.ts:27`](../../vendor/loader/src/index.ts)) diff --git a/docs/cordis-catalog/core/registry.md b/docs/cordis-api/registry.md similarity index 95% rename from docs/cordis-catalog/core/registry.md rename to docs/cordis-api/registry.md index 6b2e298c72..e6ddce5700 100644 --- a/docs/cordis-catalog/core/registry.md +++ b/docs/cordis-api/registry.md @@ -30,7 +30,7 @@ Shorthand for `ctx.plugin({ inject, apply: callback })`: the callback is unloade **Returns** the fiber; awaiting it settles once loading finished. -[Source](../../../vendor/cordis/src/registry.ts#L176) +[Source](../../vendor/cordis/src/registry.ts#L176) ### ctx.plugin(plugin, ...args) @@ -53,7 +53,7 @@ Load a plugin in the current context. **Returns** the fiber; awaiting it settles once loading finished (rejecting on config or startup errors). -[Source](../../../vendor/cordis/src/registry.ts#L185) +[Source](../../vendor/cordis/src/registry.ts#L185) ## Plugin @@ -118,7 +118,7 @@ namespace Plugin { } ``` -[Source](../../../vendor/cordis/src/registry.ts#L92) +[Source](../../vendor/cordis/src/registry.ts#L92) ## Inject @@ -149,4 +149,4 @@ namespace Inject { } ``` -[Source](../../../vendor/cordis/src/registry.ts#L19) +[Source](../../vendor/cordis/src/registry.ts#L19) diff --git a/docs/cordis-catalog/core/service.md b/docs/cordis-api/service.md similarity index 82% rename from docs/cordis-catalog/core/service.md rename to docs/cordis-api/service.md index 84b74f98df..d4b0f06da1 100644 --- a/docs/cordis-catalog/core/service.md +++ b/docs/cordis-api/service.md @@ -9,7 +9,7 @@ Base class for services that expose a named API on `ctx`. Subclasses call `super(ctx, name)` from their constructor. The service is registered immediately and is automatically removed with the owning fiber. -[Source](../../../vendor/cordis/src/service.ts#L11) +[Source](../../vendor/cordis/src/service.ts#L11) ### service.name @@ -20,7 +20,7 @@ public name!: string The service name this instance is registered under. -[Source](../../../vendor/cordis/src/service.ts#L30) +[Source](../../vendor/cordis/src/service.ts#L30) ## Static members @@ -33,7 +33,7 @@ static readonly init: unique symbol Symbol key of an instance method run after construction (class plugins). -[Source](../../../vendor/cordis/src/service.ts#L13) +[Source](../../vendor/cordis/src/service.ts#L13) ### Service.check @@ -44,7 +44,7 @@ static readonly check: unique symbol Symbol key of the availability predicate passed to `ctx.provide()`. -[Source](../../../vendor/cordis/src/service.ts#L15) +[Source](../../vendor/cordis/src/service.ts#L15) ### Service.config @@ -55,7 +55,7 @@ static readonly config: unique symbol Symbol key of the phantom intercept-config type parameter. -[Source](../../../vendor/cordis/src/service.ts#L17) +[Source](../../vendor/cordis/src/service.ts#L17) ### Service.invoke @@ -66,7 +66,7 @@ static readonly invoke: unique symbol Symbol key of the call body making a service callable (e.g. `ctx.logger()`). -[Source](../../../vendor/cordis/src/service.ts#L19) +[Source](../../vendor/cordis/src/service.ts#L19) ### Service.extend @@ -77,7 +77,7 @@ static readonly extend: unique symbol Symbol key of the helper deriving an extended service instance. -[Source](../../../vendor/cordis/src/service.ts#L21) +[Source](../../vendor/cordis/src/service.ts#L21) ### Service.tracker @@ -88,7 +88,7 @@ static readonly tracker: unique symbol Symbol key of the tracker metadata used for context tracing. -[Source](../../../vendor/cordis/src/service.ts#L23) +[Source](../../vendor/cordis/src/service.ts#L23) ### Service.resolveConfig @@ -99,4 +99,4 @@ static readonly resolveConfig: unique symbol Symbol key of the intercept-config resolution helper below. -[Source](../../../vendor/cordis/src/service.ts#L25) +[Source](../../vendor/cordis/src/service.ts#L25) diff --git a/docs/cordis-catalog/events.md b/docs/cordis-catalog/events.md deleted file mode 100644 index fb05eaa8e6..0000000000 --- a/docs/cordis-catalog/events.md +++ /dev/null @@ -1,1100 +0,0 @@ - - -# 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.` surface is the sibling [services catalog](services.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around. - -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. - -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`). - -## `agent/*` - -### `agent/created` — emit - -A fully configured agent and live session were published. Setup is composition-only; `agent/session-start` is the first startup-driving seam. Synchronous listener failure vetoes publication, while returned-promise rejection is reported. Detach requested during dispatch waits until every creation listener has observed the stable entry. - -```ts cordis-catalog -/** - * A fully configured agent and live session were published. Setup is - * composition-only; `agent/session-start` is the first startup-driving seam. - * Synchronous listener failure vetoes publication, while returned-promise - * rejection is reported. Detach requested during dispatch waits until every - * creation listener has observed the stable entry. - * @param payload.agent - the newly registered agent with its live session and completed setup. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/created'(this: Scoped, payload: { agent: Agent }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:158`](../../packages/core/agent/src/types.ts) - -### `agent/disposed` — emit - -An agent left the registry; AgentLoop emits this after driver quiescence and scoped-registration unwind, but before session detachment. Custom registry users own their driver-ordering contract. - -```ts cordis-catalog -/** - * An agent left the registry; AgentLoop emits this after driver quiescence - * and scoped-registration unwind, but before session detachment. Custom - * registry users own their driver-ordering contract. - * @param payload.agent - the exact agent removed from the registry. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/disposed'(this: Scoped, payload: { agent: Agent }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/types.ts) - -### `agent/error` — emit - -A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. - -```ts cordis-catalog -/** - * A step or turn errored. The machine reports a failure here even when - * the error has no in-turn position for a durable record. - * @param payload.agent - the agent whose turn errored. - * @param payload.turn - the turn in which the failure surfaced. - * @param payload.step - the step at which the failure surfaced. - * @param payload.error - the failure, verbatim. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - -### `agent/inbox/claimed` — emit - -One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. - -```ts cordis-catalog -/** - * One message left the inbox inside its open turn. If the proposed step - * is rejected, the claimed message ends here: it is neither discarded nor - * re-emitted as a user/message, and the turn closes without a step. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the claimed message. - * @param payload.turn - the owning turn. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) - -Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - -### `agent/inbox/discarded` — emit - -One message was discarded from the live inbox. - -```ts cordis-catalog -/** - * One message was discarded from the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the discarded message. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) - -Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - -### `agent/inbox/inserted` — emit - -One message entered the live inbox. - -```ts cordis-catalog -/** - * One message entered the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the inserted message. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) - -Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - -### `agent/pre-step` — waterfall - -Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. - -```ts cordis-catalog -/** - * Reject a proposed step or replace the messages that enter it. Calling - * `next()` preserves the current messages. - * @param payload.agent - the agent proposing the step. - * @param payload.messages - messages removed from the inbox for this step. - * @param payload.turn - the turn that will own the step. - * @param payload.step - the step proposed by the loop. - * @param payload.signal - the current turn's cancellation signal. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode waterfall - */ -'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise -``` - -Types: [Agent](../subsystems/core.md) · [PreStepDecision](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [UserMessage](../subsystems/session.md) - -Source: [`packages/core/agent/src/types.ts:230`](../../packages/core/agent/src/types.ts) - -### `agent/request` — waterfall - -Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. Model-visible content must use logged channels; this seam cannot mutate messages. - -```ts cordis-catalog -/** - * Replace the frozen call configuration. `await next()` yields the config - * the machine would use (agent options on the first request, the logged - * header afterwards); return a replacement to switch. Model-visible - * content must use logged channels; this seam cannot mutate messages. - * @param payload.agent - the agent making the model call. - * @param payload.turn - the open turn number. - * @param payload.step - the step whose request this is. - * @param payload.signal - the current turn's explicit abort signal. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode waterfall -*/ -'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise -``` - -Types: [Agent](../subsystems/core.md) · [LlmCallConfig](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) - -### `agent/request-error` — waterfall - -Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. - -```ts cordis-catalog -/** - * Handle one failed model-request attempt before the loop retries or closes - * its step. A listener returns `{ kind: 'retry' }` without calling `next()` - * when it owns recovery, or calls `next()` to delegate. The default - * `undefined` leaves the failure terminal. - * @param payload.agent - the agent whose request failed. - * @param payload.turn - the turn containing the failed request. - * @param payload.step - the step containing the failed request attempt. - * @param payload.provider - the provider selected for the failed request. - * @param payload.failure - serializable facts normalized at the final adapter boundary. - * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. - * @param payload.signal - the turn abort signal. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode waterfall - */ -'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise -``` - -Types: [Agent](../subsystems/core.md) · [LlmFailure](../subsystems/llm-streaming.md) · [RequestErrorAction](../subsystems/core.md) · [ResolvedRetryPolicy](../subsystems/llm-streaming.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) - -### `agent/session-start` — emit - -The session lifecycle began, once before the first turn. Use `agent.inject()` to seed model-facing context. This is a notification, not a veto; disposal requested by a lifecycle owner is rechecked before the driver starts. - -```ts cordis-catalog -/** - * The session lifecycle began, once before the first turn. Use - * `agent.inject()` to seed model-facing context. This is a notification, not - * a veto; disposal requested by a lifecycle owner is rechecked before the - * driver starts. - * @param payload.agent - the agent whose session lifecycle began. - * @param payload.source - why the session started (fresh startup, resume, …). - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [SessionStartSource](../subsystems/core.md) - -Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/types.ts) - -### `agent/status` — emit - -Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. - -```ts cordis-catalog -/** - * Agent status changed (`idle` ⇄ `running`). A waking delivery enters - * `running` synchronously after reserving cancellation; `idle` means no - * driver remains scheduled or active. - * @param payload.agent - the agent whose status flipped. - * @param payload.status - the status just entered (the transition's destination). - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void -``` - -Types: [Agent](../subsystems/core.md) · [AgentStatus](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/types.ts) - -### `agent/turn-stopping` — serial - -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. - -```ts cordis-catalog -/** - * The turn is about to close: the model owes no response (no live tool - * calls, no fresh steering). Awaited before the boundary commits — a - * listener that objects steers (`agent.steer(...)`) and the machine - * re-reads its inbox: fresh steering runs another step, none closes the - * turn. Data decides, so listener order cannot change the outcome. The - * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. The conclusion - * never short-circuits already-submitted next-step work: same-step - * `additionalContexts` or racing steering still runs, and the turn - * closes only when that inbox drains. - * @param payload.agent - the agent whose turn is at its stop boundary. - * @param payload.turn - the turn about to close. - * @param payload.signal - the current turn's explicit abort signal. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode serial - */ -'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void -``` - -Types: [Agent](../subsystems/core.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/core/agent/src/types.ts:277`](../../packages/core/agent/src/types.ts) - -## `agent-loop/*` - -### `agent-loop/config-start-failed` — emit - -A declarative agent entry failed before it could publish a live agent. Consumers that buffer work for the configured identity use this transient signal to reject that work instead of waiting forever. Normal factory teardown suppresses failures from the cancelled startup attempt. - -```ts cordis-catalog -/** - * A declarative agent entry failed before it could publish a live agent. - * Consumers that buffer work for the configured identity use this - * transient signal to reject that work instead of waiting forever. Normal - * factory teardown suppresses failures from the cancelled startup attempt. - * @param payload.sessionId - exact shared agent/session identity that failed startup. - * @param payload.error - persistence, setup, or publication failure. - * @mode emit - */ -'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void -``` - -Types: [SessionId](../subsystems/core.md) - -Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) - -## `approval/*` - -### `approval/request` — waterfall - -Ask composed answerers for one decision. Return an outcome to claim the request or call `next()`; failure yields the fail-closed default. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - -```ts cordis-catalog -/** - * Ask composed answerers for one decision. Return an outcome to claim the - * request or call `next()`; failure yields the fail-closed default. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @param req - the pending decision (agent, tool identity, reason, signal). - * @mode waterfall - */ -'approval/request'(this: Scoped, req: ApprovalRequest, next: () => Promise): Promise -``` - -Types: [ApprovalOutcome](../subsystems/approval.md) · [ApprovalRequest](../subsystems/approval.md) · [ApprovalService](../subsystems/approval.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/interaction/user-approval/src/index.ts:30`](../../packages/interaction/user-approval/src/index.ts) - -## `commands/*` - -### `commands/change` — emit - -A command was registered or unregistered. This is an unfiltered registry notification because a global or scoped change may affect any UI view. Observer failures are contained and cannot veto the registry mutation. - -```ts cordis-catalog -/** - * A command was registered or unregistered. This is an unfiltered registry - * notification because a global or scoped change may affect any UI view. - * Observer failures are contained and cannot veto the registry mutation. - * @mode emit - */ -'commands/change'(): void -``` - -Source: [`packages/interaction/commands/src/index.ts:172`](../../packages/interaction/commands/src/index.ts) - -## `credentials/*` - -### `credentials/updated` — emit - -Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. - -```ts cordis-catalog -/** - * Committed change to a provider-managed credential source: a `set`, an - * `unset`, or an external edit observed in storage. Ambient - * process-environment changes are not observable and never emit. Listener - * failures are contained and logged — a sync throw and an async rejection - * alike — without changing the committed operation's outcome, except - * `INVARIANT`-coded failures, which rethrow after every listener ran; - * that rethrow reaches the emitter only from synchronous listeners, so - * invariant checks on this event must not be async functions. - * @param ref - the reference whose stored value changed. - * @mode emit - */ -'credentials/updated'(ref: CredentialRef): void -``` - -Types: [CredentialRef](../subsystems/credentials.md) - -Source: [`packages/credentials/credentials/src/index.ts:67`](../../packages/credentials/credentials/src/index.ts) - -## `domain/*` - -### `domain/changed` — emit - -A domain record or the global singleton changed, emitted once per write strictly after the backend acknowledged durability. Events of one domain arrive in its write-chain order. - -```ts cordis-catalog -/** - * A domain record or the global singleton changed, emitted once per write - * strictly after the backend acknowledged durability. Events of one - * domain arrive in its write-chain order. - * @param change - domain, table (`''` for global), key (`''` for global), - * operation discriminant, and on `put` the new snapshot. - * @mode emit - */ -'domain/changed'(change: DomainChanged): void -``` - -Types: [DomainChanged](../subsystems/storage.md) - -Source: [`packages/storage/storage-domain/src/events.ts:46`](../../packages/storage/storage-domain/src/events.ts) - -## `fs/*` - -### `fs/edit-intent` — waterfall - -Single-slot decision for the next FileSystem.editText. Calling `next()` yields an unconditional edit; the first returned guard wins. - -```ts cordis-catalog -/** - * Single-slot decision for the next {@link FileSystem.editText}. Calling - * `next()` yields an unconditional edit; the first returned guard wins. - * @param target - the resolved target about to be edited. - * @param actor - the opaque tool-execution context the decider keys off. - * @mode waterfall - */ -'fs/edit-intent'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined> -``` - -Types: [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) - -Source: [`packages/fs/fs/src/index.ts:64`](../../packages/fs/fs/src/index.ts) - -### `fs/observed` — emit - -Record a successful observation. Listeners must be synchronous recorders: throws fail the tool call and returned promises are not awaited. - -```ts cordis-catalog -/** - * Record a successful observation. Listeners must be synchronous recorders: - * throws fail the tool call and returned promises are not awaited. - * @param target - the target that was read/written/edited. - * @param version - the version the actor now holds as its observation. - * @param actor - the observing tool-execution context; undefined records nothing useful. - * @mode emit - */ -'fs/observed'(target: FsTarget, version: FsVersion, actor: object | undefined): void -``` - -Types: [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) - -Source: [`packages/fs/fs/src/index.ts:73`](../../packages/fs/fs/src/index.ts) - -### `fs/write-intent` — waterfall - -Single-slot decision for the next FileSystem.writeText. Calling `next()` yields the bare provider's unconditional write; the first listener that returns an intent owns the decision rather than composing with peers. - -```ts cordis-catalog -/** - * Single-slot decision for the next {@link FileSystem.writeText}. Calling - * `next()` yields the bare provider's unconditional write; the first listener - * that returns an intent owns the decision rather than composing with peers. - * @param target - the resolved target about to be written. - * @param actor - the opaque tool-execution context the decider keys off. - * @mode waterfall - */ -'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise -``` - -Types: [FsTarget](../subsystems/filesystem.md) · [FsWriteIntent](../subsystems/filesystem.md) - -Source: [`packages/fs/fs/src/index.ts:56`](../../packages/fs/fs/src/index.ts) - -## `goal/*` - -### `goal/changed` — emit - -Goal mutation accepted by one live agent. The matching `goal/change` session event has already committed. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - -```ts cordis-catalog -/** - * Goal mutation accepted by one live agent. The matching `goal/change` - * session event has already committed. Listener failures are contained. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @param payload.agent - agent whose session owns the goal. - * @param payload.change - fresh current projection or clear tombstone. - * @mode emit - */ -'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped, payload: { agent: Agent; change: GoalChanged }): void -``` - -Types: [Agent](../subsystems/core.md) · [GoalChanged](../subsystems/goal.md) · [Scoped](../subsystems/scope.md) - -Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts) - -## `llm/*` - -### `llm/adapters-updated` — emit - -The provider topology changed: an adapter registered or unregistered routes, or the configurable-provider directory gained or lost entries. This is a payload-free registry notification fired at each commit point (including registration disposal); consumers re-read `listProviders()`, `listModels()`, or `listConfigurableProviders()` for the new state. Observer failures are contained and cannot veto the registry mutation. - -```ts cordis-catalog -/** - * The provider topology changed: an adapter registered or unregistered - * routes, or the configurable-provider directory gained or lost entries. - * This is a payload-free registry notification fired at each commit point - * (including registration disposal); consumers re-read `listProviders()`, - * `listModels()`, or `listConfigurableProviders()` for the new state. - * Observer failures are contained and cannot veto the registry mutation. - * @mode emit - */ -'llm/adapters-updated'(): void -``` - -Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) - -### `llm/stream` — waterfall - -Waterfall around every streaming model call (retry, replay, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit. - -```ts cordis-catalog -/** - * Waterfall around every streaming model call (retry, replay, routing). - * Bound to the {@link LlmService}; call `next()` to reach the resolved - * adapter's stream, or yield your own chunks to short-circuit. - * @param options - the full request. A LOOP-built request carries the - * process-local {@link markAgentLoopRequest} identity and arrives deep-frozen - * (mutation throws): its content is a pure function of the session log (the - * reconstructability Agent Note), so listeners read it, never rewrite it. - * Hand-built calls do not carry that marker; their messages already obey - * the immutable creation contract. - * @mode waterfall - */ -'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable -``` - -Types: [GenerateOptions](../subsystems/core.md) · [LlmService](../subsystems/llm-streaming.md) · [StreamChunk](../subsystems/llm-streaming.md) - -Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) - -## `session/*` - -### `session/created` — emit - -Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context. - -```ts cordis-catalog -/** - * Creation announcement during session publication. A synchronous throw vetoes and rolls - * back with a paired disposal; detach requested during dispatch is deferred. - * A returned-promise rejection is logged but cannot retroactively veto this - * synchronous boundary. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners - * receive only sessions entered through that agent's context. - * @param session - the session just entered and announced. - * @dshScopeScan unsupported - * @mode emit - */ -'session/created'(this: Scoped, session: Session): void -``` - -Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) - -Source: [`packages/core/session/src/index.ts:74`](../../packages/core/session/src/index.ts) - -### `session/disposed` — emit - -Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. - -```ts cordis-catalog -/** - * Emitted once when an announced session leaves the store, including - * publication rollback, but never for an entry whose creation announcement - * did not begin. Listener failures are logged and contained. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. - * @param session - the session that is no longer live in the store. - * @dshScopeScan unsupported - * @mode emit - */ -'session/disposed'(this: Scoped, session: Session): void -``` - -Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) - -Source: [`packages/core/session/src/index.ts:84`](../../packages/core/session/src/index.ts) - -### `session/event` — emit - -Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context. - -```ts cordis-catalog -/** - * Post-commit, fire-and-forget append feed. The listener snapshot resolves - * before the log push, but callbacks run after it; observer failures are - * logged and contained without making the committed append fail. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners - * receive only events from sessions entered through that agent's context. - * @param session - the session whose log grew. - * @param event - the appended event, exactly as recorded. - * @dshScopeScan unsupported - * @mode emit - */ -'session/event'(this: Scoped, session: Session, event: SessionEvent): void -``` - -Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md) - -Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) - -### `session/flush` — parallel - -Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. - -```ts cordis-catalog -/** - * Awaited parallel durability checkpoint: every listener runs and the - * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch - * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. - * @param session - the session whose buffered events must reach durable storage. - * @dshScopeScan unsupported - * @mode parallel - */ -'session/flush'(this: Scoped, session: Session): Promise | void -``` - -Types: [Scoped](../subsystems/scope.md) · [Session](../subsystems/session.md) - -Source: [`packages/core/session/src/index.ts:105`](../../packages/core/session/src/index.ts) - -## `settings/*` - -### `settings/document-updated` — emit - -One registered namespace's RAW user section changed, whether or not the resolved value did. `settings/updated` is the consumer-facing event and stays deep-equal-gated; this one exists for configuration surfaces, which must learn that a field went from inherited to overridden (same resolved value, different meaning) and that their held revision is stale. Listener containment matches `settings/updated`. - -```ts cordis-catalog -/** - * One registered namespace's RAW user section changed, whether or not the - * resolved value did. `settings/updated` is the consumer-facing event and - * stays deep-equal-gated; this one exists for configuration surfaces, - * which must learn that a field went from inherited to overridden (same - * resolved value, different meaning) and that their held revision is - * stale. Listener containment matches `settings/updated`. - * @param ns - the namespace whose stored section changed. - * @param revision - the namespace's new revision. - * @mode emit - */ -'settings/document-updated'(ns: SettingsNamespace, revision: number): void -``` - -Types: [SettingsNamespace](../subsystems/settings.md) - -Source: [`packages/settings/settings/src/index.ts:170`](../../packages/settings/settings/src/index.ts) - -### `settings/updated` — emit - -Committed change to one registered namespace's resolved value. Emitted after the provider persisted (for `update`) or published (`provider`) the change; never emitted when the resolved value is deep-equal. Listener failures are contained and logged — a sync throw and an async rejection alike — except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. - -```ts cordis-catalog -/** - * Committed change to one registered namespace's resolved value. Emitted - * after the provider persisted (for `update`) or published (`provider`) - * the change; never emitted when the resolved value is deep-equal. - * Listener failures are contained and logged — a sync throw and an async - * rejection alike — except `INVARIANT`-coded failures, which rethrow - * after every listener ran; that rethrow reaches the emitter only from - * synchronous listeners, so invariant checks on this event must not be - * async functions. - * @param ns - the namespace whose resolved value changed. - * @param next - the new resolved value. - * @param prev - the previous resolved value. - * @param source - whether the change entered through `update()` or the provider. - * @mode emit - */ -'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void -``` - -Types: [SettingsNamespace](../subsystems/settings.md) · [SettingsUpdateSource](../subsystems/settings.md) - -Source: [`packages/settings/settings/src/index.ts:157`](../../packages/settings/settings/src/index.ts) - -## `skills/*` - -### `skills/change` — emit - -A skill provider, runtime contribution, or provider-backed catalog may have changed. This is an unfiltered invalidation notification; consumers refetch the catalog for their own lookup options. Listener failures are contained and cannot veto the registry mutation. - -```ts cordis-catalog -/** - * A skill provider, runtime contribution, or provider-backed catalog may - * have changed. This is an unfiltered invalidation notification; consumers - * refetch the catalog for their own lookup options. Listener failures are - * contained and cannot veto the registry mutation. - * @mode emit - */ -'skills/change'(): void -``` - -Source: [`packages/skill/skill/src/index.ts:283`](../../packages/skill/skill/src/index.ts) - -## `subagent/*` - -### `subagent/end` — emit - -A published child settled. Scope-filtered dispatch uses the same delegating parent carrier as `subagent/start`, so the lifecycle pair reaches the same scoped audience. - -```ts cordis-catalog -/** - * A published child settled. Scope-filtered dispatch uses the same delegating - * parent carrier as `subagent/start`, so the lifecycle pair reaches the - * same scoped audience. - * @param info - the run identity and terminal outcome. - * @dshScopeScan unsupported - * @mode emit - */ -'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void -``` - -Types: [Scoped](../subsystems/scope.md) · [SubagentService](../subsystems/subagent.md) - -Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts) - -### `subagent/provider-added` — emit - -A provider became resolvable in the registry. - -```ts cordis-catalog -/** - * A provider became resolvable in the registry. - * @param provider - the registered provider. - * @mode emit - */ -'subagent/provider-added'(provider: SubagentProvider): void -``` - -Types: [SubagentProvider](../subsystems/subagent.md) - -Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts) - -### `subagent/provider-removed` — emit - -A provider left the registry. Accepted runs remain holder-owned. - -```ts cordis-catalog -/** - * A provider left the registry. Accepted runs remain holder-owned. - * @param name - the provider name that no longer resolves. - * @mode emit - */ -'subagent/provider-removed'(name: string): void -``` - -Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts) - -### `subagent/start` — emit - -A provider established a published child. For in-process providers, `ctx.agents.get(info.id)` resolves during this notification. Scope-filtered dispatch keys the carrier by the delegating parent, so a parent-scoped listener observes only its own delegations. Paired with `subagent/end`. - -```ts cordis-catalog -/** - * A provider established a published child. For in-process providers, - * `ctx.agents.get(info.id)` resolves during this notification. - * Scope-filtered dispatch keys the carrier by the delegating parent, so a - * parent-scoped listener observes only its own delegations. Paired with - * `subagent/end`. - * @param info - the provider and published child identity. - * @dshScopeScan unsupported - * @mode emit - */ -'subagent/start'(this: Scoped, info: SubagentRunInfo): void -``` - -Types: [Scoped](../subsystems/scope.md) · [SubagentService](../subsystems/subagent.md) - -Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts) - -## `system-prompt/*` - -### `system-prompt/assemble` — waterfall - -Expert waterfall over the assembled sections, contexts, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative. A supplied signal controls only this explicit assembly request and must not be retained to control later turns. - -```ts cordis-catalog -/** - * Expert waterfall over the assembled sections, contexts, tools, and variables. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners - * receive only that scope's assemblies. The returned value is authoritative. - * A supplied signal controls only this explicit assembly request and must not - * be retained to control later turns. - * @param assembly - the mutable assembly built from registered providers. - * @param context - the caller's per-assembly context. - * @mode waterfall - */ -'system-prompt/assemble'(this: Scoped, assembly: PromptAssembly, context: AssembleContext, next: () => Promise): Promise -``` - -Types: [AssembleContext](../subsystems/system-prompt.md) · [Scoped](../subsystems/scope.md) · [SystemPrompt](../subsystems/system-prompt.md) - -Source: [`packages/core/system-prompt/src/index.ts:29`](../../packages/core/system-prompt/src/index.ts) - -### `system-prompt/change` — emit - -Emitted when any prompt provider changes. This registry notification is unfiltered because a global change affects every scope. - -```ts cordis-catalog -/** - * Emitted when any prompt provider changes. This registry notification is - * unfiltered because a global change affects every scope. - * @mode emit - */ -'system-prompt/change'(): void -``` - -Source: [`packages/core/system-prompt/src/index.ts:35`](../../packages/core/system-prompt/src/index.ts) - -## `telemetry/*` - -### `telemetry/record` — waterfall - -Transform one outbound record before it reaches the backend. This waterfall is the seam's redaction extension point. It ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Live capture dispatches at append time; on-demand capture dispatches while reading the canonical log. Redaction applies to the exported copy only; the canonical session log is never rewritten. - -```ts cordis-catalog -/** - * Transform one outbound record before it reaches the backend. This - * waterfall is the seam's redaction extension point. It ships NO rules - * of its own: the - * innermost `next()` passes the record through unchanged, and with no - * listener mounted records reach the backend as captured, so exported - * data is exactly as clean as the rules a deployment mounts. Listeners - * stack by transforming `next()`'s return value; returning without - * `next()` replaces everything beneath. Dispatched synchronously on the - * capture hot path inside the coordinator's containment: a throwing - * listener withholds that one record (fail-closed) and never reaches the - * agent loop. Live capture dispatches at append time; on-demand capture - * dispatches while reading the canonical log. Redaction applies to the - * exported copy only; the canonical session log is never rewritten. - * @param record - the candidate record, already the coordinator's own deep - * copy; listeners return a (possibly new) record and must not mutate it. - * @mode waterfall - */ -'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) - -## `tools/*` - -### `tools/change` — emit - -A tool was registered or unregistered, or a scoped restriction changed (the available tool set changed — possibly for one scope only). An UNFILTERED registry-subject notification, deliberately not scope-filtered dispatch: a global change concerns every agent's next assembly, so a scoped listener subscribing here sees every change, not just its own scope's. - -```ts cordis-catalog -/** - * A tool was registered or unregistered, or a scoped restriction changed - * (the available tool set changed — possibly for one scope only). An - * UNFILTERED registry-subject notification, deliberately not scope-filtered - * dispatch: a global change concerns every agent's next assembly, so a - * scoped listener subscribing here sees every change, not just its own - * scope's. - * @mode emit - */ -'tools/change'(): void -``` - -Source: [`packages/core/tools/src/index.ts:191`](../../packages/core/tools/src/index.ts) - -### `tools/code-dispatch-log` — waterfall - -Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bridge appends its `tool/code-dispatch` event. `next()` keeps the content unchanged; a listener may return replacement blocks (e.g. the spill policy's preview + locator for an oversized text result). Only the logged copy is affected — the program already received the complete value, and the model sees neither. A throwing listener is contained: the bridge falls back to logging the unshaped content. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. - -```ts cordis-catalog -/** - * Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before - * the bridge appends its `tool/code-dispatch` event. `next()` keeps the - * content unchanged; a listener may return replacement blocks (e.g. the - * spill policy's preview + locator for an oversized text result). Only the - * logged copy is affected — the program already received the complete - * value, and the model sees neither. A throwing listener is contained: - * the bridge falls back to logging the unshaped content. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. - * @param dispatch - the parent execution, sub-call identity, and the settled content to log. - * @mode waterfall - */ -'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise -``` - -Types: [CodeDispatchLog](../subsystems/tools.md) · [ContentBlock](../subsystems/core.md) · [Scoped](../subsystems/scope.md) · [ToolRegistry](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) - -### `tools/execute` — waterfall - -Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. The registry re-fuses the original caller signal before the body, so replacement cannot detach caller cancellation; wrappers must still restore their signal and reach quiescence. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - -```ts cordis-catalog -/** - * Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns - * a normalized result; wrappers may change only `exec.signal`, while call - * identity remains immutable. The registry re-fuses the original caller - * signal before the body, so replacement cannot detach caller cancellation; - * wrappers must still restore their signal and reach quiescence. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - * @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal). - * @mode waterfall - */ -'tools/execute'(this: Scoped, exec: ToolDispatchExecution, next: () => Promise): Promise -``` - -Types: [Scoped](../subsystems/scope.md) · [ToolDispatchExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:148`](../../packages/core/tools/src/index.ts) - -### `tools/post-execute` — waterfall - -Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with the code selected by whether the tool body was invoked. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - -```ts cordis-catalog -/** - * Accept, replace, enrich, or block a normalized dispatch result. `next()` - * accepts it unchanged; thrown tools still reach this seam as errors. Async - * listeners must observe `exec.signal`; after they settle, caller - * cancellation replaces only a successful accepted outcome with the code - * selected by whether the tool body was invoked. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - * @param exec - the call that just ran (name, parsed arguments, caller agent). - * @param result - the dispatch outcome a listener may accept, replace, or block. - * @mode waterfall - */ -'tools/post-execute'(this: Scoped, exec: ToolExecution, result: Readonly, next: () => Promise): Promise -``` - -Types: [PostToolDecision](../subsystems/tools.md) · [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:160`](../../packages/core/tools/src/index.ts) - -### `tools/pre-execute` — waterfall - -Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Async gates must observe `exec.signal`; the registry rechecks cancellation after they settle but never abandons their promise. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - -```ts cordis-catalog -/** - * Allow, deny, or ask before dispatch. `next()` delegates to allow; missing - * approval support turns `ask` into denial. Async gates must observe - * `exec.signal`; the registry rechecks cancellation after they settle but - * never abandons their promise. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. - * @param exec - the pending call (name, parsed arguments, caller agent). - * @mode waterfall - */ -'tools/pre-execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise -``` - -Types: [PreToolDecision](../subsystems/tools.md) · [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:137`](../../packages/core/tools/src/index.ts) - -### `tools/result` — emit - -Observe the frozen, lossless-JSON final outcome. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. - -```ts cordis-catalog -/** - * Observe the frozen, lossless-JSON final outcome. Listener failures are contained. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. - * @param exec - the execution object that traversed the pipeline. - * @param result - a deep-frozen snapshot of the final returned result. - * @mode emit - */ -'tools/result'(this: Scoped, exec: Readonly, result: Readonly): undefined -``` - -Types: [Scoped](../subsystems/scope.md) · [ToolExecution](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolRegistry](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:181`](../../packages/core/tools/src/index.ts) - -## `workflow/*` - -### `workflow/agent-end` — emit - -One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`. - -```ts cordis-catalog -/** - * One `agent()` call settled (clean result, child failure, or run - * cancellation). Paired with {@link Events['workflow/agent-start']} by - * `agent.seq`, exactly once per started call on every stop path — on an - * engine termination path (a worker killed past its grace) the end is - * engine-synthesized with outcome `'cancelled'`. - * @param info - the run's identity snapshot. - * @param agent - the call identity plus its outcome. - * @mode emit - */ -'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:81`](../../packages/workflow/workflow/src/index.ts) - -### `workflow/agent-start` — emit - -One `agent()` call established a published child run. Paired with Events['workflow/agent-end'] by `agent.seq`. A call that never receives a published run from the provider emits neither event in this pair. - -```ts cordis-catalog -/** - * One `agent()` call established a published child run. Paired with - * {@link Events['workflow/agent-end']} by `agent.seq`. A call that never - * receives a published run from the provider emits neither - * event in this pair. - * @param info - the run's identity snapshot. - * @param agent - the call's sequence number, label, phase, and child id. - * @mode emit - */ -'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:70`](../../packages/workflow/workflow/src/index.ts) - -### `workflow/end` — emit - -A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start']. - -```ts cordis-catalog -/** - * A workflow run settled (any stop reason). Fired when - * {@link WorkflowRun.result} resolves. Paired with - * {@link Events['workflow/start']}. - * @param info - the run's identity snapshot. - * @param result - the outcome data (stop reason, error, agent count) — - * deliberately WITHOUT the result value (see {@link WorkflowResultInfo}). - * @mode emit - */ -'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:91`](../../packages/workflow/workflow/src/index.ts) - -### `workflow/log` — emit - -The script emitted a narration line (a `log(message)` call). - -```ts cordis-catalog -/** - * The script emitted a narration line (a `log(message)` call). - * @param info - the run's identity snapshot. - * @param message - the logged message, verbatim. - * @mode emit - */ -'workflow/log'(info: WorkflowRunInfo, message: string): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:60`](../../packages/workflow/workflow/src/index.ts) - -### `workflow/phase` — emit - -The script entered a phase (a `phase(title)` call) — progress grouping for observers; no execution semantics. - -```ts cordis-catalog -/** - * The script entered a phase (a `phase(title)` call) — progress grouping - * for observers; no execution semantics. - * @param info - the run's identity snapshot. - * @param title - the phase title, verbatim. - * @mode emit - */ -'workflow/phase'(info: WorkflowRunInfo, title: string): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:53`](../../packages/workflow/workflow/src/index.ts) - -### `workflow/start` — emit - -A workflow run started — the script's meta block validated, the body about to execute. Paired with Events['workflow/end']. - -```ts cordis-catalog -/** - * A workflow run started — the script's meta block validated, the body - * about to execute. Paired with {@link Events['workflow/end']}. - * @param info - the run's identity snapshot (id + meta). - * @mode emit - */ -'workflow/start'(info: WorkflowRunInfo): void -``` - -Types: [WorkflowRunInfo](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:45`](../../packages/workflow/workflow/src/index.ts) - -## 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. - -- `internal/plugin` — A plugin fiber was created. ([`vendor/cordis/src/events.ts:328`](../../vendor/cordis/src/events.ts)) -- `internal/status` — A fiber changed lifecycle state. ([`vendor/cordis/src/events.ts:330`](../../vendor/cordis/src/events.ts)) -- `internal/service` — Interception hook for a service binding (no core producer). ([`vendor/cordis/src/events.ts:332`](../../vendor/cordis/src/events.ts)) -- `internal/update` — Waterfall: a fiber config update is being applied. ([`vendor/cordis/src/events.ts:334`](../../vendor/cordis/src/events.ts)) -- `internal/get` — Waterfall: a service is being read from the store. ([`vendor/cordis/src/events.ts:336`](../../vendor/cordis/src/events.ts)) -- `internal/set` — Waterfall: a service is being written to the store. ([`vendor/cordis/src/events.ts:338`](../../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)) -- `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:21`](../../vendor/hmr/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/entry-init` — A config entry is being initialized. ([`vendor/loader/src/index.ts:25`](../../vendor/loader/src/index.ts)) -- `loader/partial-dispose` — An entry is being partially disposed on reload. ([`vendor/loader/src/index.ts:26`](../../vendor/loader/src/index.ts)) -- `loader/patch-context` — A context is being patched during a reload. ([`vendor/loader/src/index.ts:27`](../../vendor/loader/src/index.ts)) diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md deleted file mode 100644 index 88f1762cff..0000000000 --- a/docs/cordis-catalog/services.md +++ /dev/null @@ -1,2917 +0,0 @@ - - -# Cordis Services Catalog - -Every `ctx.` 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. - -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. - -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). - -## `ctx.agentLoop` — `AgentLoop` - -Concrete agent factory and driver service. - -```ts cordis-catalog -/** - * Create an agent and session under one caller-supplied identity, owned by - * the accessing fiber. Constructor-driven config calls mint a fresh combined - * id before entering this boundary. - * @param id - shared agent/session identity. - * @param options - concrete loop options. - * @param meta - optional fresh-session workspace metadata. - * @returns the published running agent. - */ -create(id: SessionId, options: AgentOptions = {}, meta: Pick = {}): Agent - -/** - * Create an owned agent on a caller-supplied session id. - * @param ownerCtx - caller context that structurally owns the lifecycle. - * @param options - identities, session seed/metadata, loop options, setup, and cancellation. - * @returns the published handle. - */ -async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise - -/** - * Resume an owned agent from the configured persistence service. - * @param ownerCtx - caller context that owns load, setup, and the live lifecycle. - * @param options - persisted identity, loop options, setup, and cancellation. - * @returns the published handle. - */ -async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise -``` - -Types: [Agent](../subsystems/core.md) · [AgentOptions](../subsystems/core.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) - -Source: [`packages/core/agent-loop/src/index.ts:277`](../../packages/core/agent-loop/src/index.ts) - -## `ctx.agents` — `AgentRegistry` - -Agent service (`ctx.agents`): tracks live agents and carries the initiating Agent through one process-local asynchronous driver chain. Agent *creation* is provided by whichever plugin implements the AgentFactory (`@deepseek-ai/dsh-agent-loop`), registered via setFactory. - -Initiator methods provide same-process causal attribution only. Ambient presence is neither liveness proof nor authorization; subjects and owners remain explicit, as does identity at worker, process, persistence, and wire boundaries. Returned Promise boundaries drain during teardown, except a nested lineage that starts an owning-fiber unload is excluded from its own drain. - -```ts cordis-catalog -/** - * Read the Agent that initiated the inherited asynchronous driver chain. - * Use this optional form for logging, tracing, metrics, or host attribution - * that also supports agentless calls. When a parent creates a child, setup - * reports the causal parent while `agentCtx.agent` identifies the child. - * @returns the inherited Agent, or `undefined` outside an initiator boundary - * and inside an explicit clearing boundary. - * @throws when this service instance has been disposed. - */ -currentInitiator(): Agent | undefined - -/** - * Read the initiating Agent and fail when no initiator boundary is active. - * Use this for private helpers contractually below a driver, or for a - * deployment-owned outbound request whose contract forbids agentless calls. - * Generic or direct-call seams use optional lookup or explicit request fields. - * @returns the inherited Agent. - * @throws when no initiator is active or this service instance has been disposed. - */ -requireInitiator(): Agent - -/** - * Run an operation with one exact Agent as its process-local initiator. The - * exact synchronous value or Promise returned by the operation is preserved. - * Custom drivers and test harnesses wrap their complete returned foreground - * lifetime. - * A queue or wire receiver may establish this boundary only after validating - * explicit identity and resolving the exact live Agent; this method does neither. - * Detached work remains owned by the subsystem that starts it. - * @param agent - initiating Agent to inherit; presence is neither liveness proof nor authorization. - * @param operation - synchronous or asynchronous operation to invoke. - * @returns the exact value returned by `operation`. - * @throws when the initiator scope is closing/disposed, or when `operation` throws. - */ -withInitiator(agent: Agent, operation: () => T): T - -/** - * Run an operation inside a boundary that hides any inherited initiating - * Agent. The exact synchronous value or Promise is preserved. - * Use this while creating lazy shared timers, queue pumps, pool maintenance, - * watchers, or exporters so they do not inherit the first Agent that happens - * to initialize them. It clears only initiator attribution, not explicit - * fields, and does not own or drain detached resources. - * @param operation - synchronous or asynchronous operation to invoke without an initiator. - * @returns the exact value returned by `operation`. - * @throws when the initiator scope is closing/disposed, or when `operation` throws. - */ -withoutInitiator(operation: () => T): T - -/** - * Register the agent-creation factory (the loop calls this on construction, - * effect-scoped). A traced Cordis service is canonicalized to its concrete - * target; each create/resume call is then traced through that caller's - * context so ownership follows the caller without stacking proxy layers. - * Throws if a factory is already registered. Returns the disposer; on - * dispose the factory slot is cleared. - * @param factory - the loop-owned factory {@link create}/{@link resume} delegate to. - * @returns the disposer that clears the factory slot. The exact - * Cordis effect disposer (single-shot): composite (generator) effects may - * yield it directly — exact identity nests the teardown in order. - */ -setFactory(factory: AgentFactory): () => void - -/** - * Create and publish a new agent through the registered factory. - * Distinct from {@link register} (which records an already-constructed - * agent): this constructs the agent and its session. Rejects if no factory is - * registered or creation/setup fails. The resolved {@link AgentHandle} lets - * the owner tear down exactly this agent. - * @param options - shared identity, session seed/metadata, and agent options. - * @returns the handle after setup, rollback-covered publication, and loop start complete. - */ -async create(options: CreateAgentOptions): Promise - -/** - * Load a persisted session and resume an agent on it through the registered - * factory. Rejects if no factory is registered; the factory rejects if - * session persistence is not configured or persistence/setup fails. - * @param options - persisted identity, configuration, and optional setup. - * @returns the handle after setup, rollback-covered publication, and loop start complete. - */ -async resume(options: ResumeAgentOptions): Promise - -/** - * Register a live agent. Throws if an agent with the same id is already - * registered. Emits `agent/created` on registration and `agent/disposed` - * when the calling fiber is disposed — both with the agent's scope carrier - * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the - * emits are scope-filtered regardless of which context invoked `register` - * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always - * requires passing the carrier). Returns the disposer. - * @param agent - the already-constructed agent to record in the store. - * @returns the EXACT Cordis effect disposer (single-shot; a repeat call - * returns undefined without awaiting an in-flight teardown). Exact - * identity is load-bearing: a composite (generator) effect that owns a - * teardown ORDER — the agent factory's lifecycle chain — must yield THIS - * function so Cordis nests the unregistration at that yield position; - * yielding a wrapper would leave it disposing as a concurrent sibling on - * owner unload, unregistering the agent (and emitting `agent/disposed`) - * while its final turn is still draining. - */ -register(agent: Agent): () => void - -/** - * Insert an already-constructed agent without announcing it. This is the - * advanced ordered-lifecycle primitive used by the async agent factory: it - * first completes setup while the agent is unpublished, then assigns the - * returned detach closure into its pre-installed composite teardown before - * calling {@link announce}. Ordinary callers use {@link register}. - * @param agent - the prepared, unpublished agent. - * @param owner - live agent whose scoped context created this agent, or - * undefined for a top-level runtime root. This is runtime ownership, not - * the resumed session's durable parent lineage. - * @returns an idempotent closure that removes this exact entry and emits - * `agent/disposed` with listener failures contained. When called from a - * synchronous `agent/created` listener, removal and disposal wait until - * that creation dispatch unwinds. - */ -enter(agent: Agent, owner: Agent | undefined): () => void - -/** - * Announce an agent previously inserted with {@link enter}. - * @param agent - the live inserted agent to announce. - * @throws if `agent` is not the exact live registry entry for its id, or its - * creation announcement already began (including a reentrant call from a - * creation listener). - */ -announce(agent: Agent): void - -/** - * Look up a live agent. - * @param id - the shared agent/session id to look up. - * @returns the agent, or undefined when no live agent has that id. - */ -get(id: SessionId): Agent | undefined - -/** - * Test whether a live agent was created through one exact parent agent's - * scoped context. Runtime ownership is independent of durable session - * lineage and remains unambiguous when unrelated providers reuse an id. - * @param id - the candidate child agent's shared agent/session id. - * @param owner - the expected runtime creator agent. - * @returns true only while the exact child entry is live under that owner. - */ -isOwnedBy(id: SessionId, owner: Agent): boolean - -/** - * All live agents, in registration order. - * @returns a fresh array; mutating it does not affect the registry. - */ -list(): Agent[] - -/** - * All live top-level agents in registration order. A top-level agent was - * created without an owning agent context; durable session lineage does not - * affect this runtime relation, so a resumed fork may still be a root. - * @returns a fresh array; mutating it does not affect the registry. - */ -roots(): Agent[] -``` - -Types: [Agent](../subsystems/core.md) · [SessionId](../subsystems/core.md) - -Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) - -## `ctx.approval` — `ApprovalService` - -Approval service that applies session policy before answerers and logs every ask/outcome pair to the requesting session. It exposes deterministic policy changes to the model through the runtime-context snapshot and switch notices. - -```ts cordis-catalog -/** - * Switch one live agent's policy and queue the transition for its next model - * step. Session initialization uses {@link setApprovalPolicy} directly - * because there is no previously visible policy to change. - * @param agent - the live agent whose policy is changing. - * @param policy - the new effective policy. - */ -setPolicy(agent: Agent, policy: ApprovalPolicy): void - -/** - * Ask the composed answerers to decide one readonly same-process request. - * The service borrows the request, agent, session, and live signal directly. - * The request requires an open turn because the audit pair must be enclosed - * by the durable log's commit/replay boundary; an idle ask rejects before - * appending anything. The answerer phase always produces an outcome: an - * aborted signal yields `'cancelled'`, a missing or throwing answerer yields - * `'unavailable'` (fail closed), and a rogue non-vocabulary return value is - * normalized to `'unavailable'`. A failure that prevents either audit append - * from committing still rejects because returning an unlogged decision would - * violate the pair. Session contains post-commit observer failures, so an - * authoritative append cannot reject the request or suppress its matching - * audit event. - * @param req - the pending decision (agent, tool identity, reason, signal). - * @returns the closed outcome; `'allowed-once'` is the only grant. - * @throws when no turn is open or either audit event fails before the session - * append commit point. - */ -async request(req: ApprovalRequest): Promise - -/** - * Read the session override without applying the configured default. - * @param session - session whose log supplies the override. - * @returns the last logged policy, or `undefined` without one. - */ -overrideOf(session: Session): ApprovalPolicy | undefined -``` - -Types: [Agent](../subsystems/core.md) · [ApprovalOutcome](../subsystems/approval.md) · [ApprovalPolicy](../subsystems/approval.md) · [ApprovalRequest](../subsystems/approval.md) · [Session](../subsystems/session.md) - -Source: [`packages/interaction/user-approval/src/index.ts:193`](../../packages/interaction/user-approval/src/index.ts) - -## `ctx.bash` — `BashExecutor` (abstract seam) - -Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). - -Implementations must honor these semantics: - -- run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult. -- start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr. -- BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files. -- A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload. - -```ts cordis-catalog -/** - * Apply implementation-owned defaults and caps to a request before execution. - * @param request - the caller's request; omitted fields get this - * implementation's defaults, capped fields are clamped. - * @returns the fully-specified spec to hand to {@link run}/{@link start}. - */ -abstract resolve(request: BashExecRequest): BashExecSpec - -/** - * Run a command in the foreground; resolves when it finishes. - * @param spec - a resolved spec from {@link resolve}, never a raw request. - * @returns the outcome; nonzero exits, timeout kills, and abort kills - * resolve with a descriptive result rather than reject. - */ -abstract run(spec: BashExecSpec): Promise - -/** - * Start a background process and return its handle immediately. - * @param spec - a resolved spec from {@link resolve}, never a raw request. - * @returns the live process handle (reads, kill, quiescence promise). - */ -abstract start(spec: BashExecSpec): BashProcess -``` - -Types: [BashExecRequest](../subsystems/bash.md) · [BashExecSpec](../subsystems/bash.md) · [BashProcess](../subsystems/bash.md) · [BashRunResult](../subsystems/bash.md) - -Source: [`packages/bash/bash/src/index.ts:53`](../../packages/bash/bash/src/index.ts) - -## `ctx.bashEnv` — `BashEnvRegistry` - -Registry (`ctx.bashEnv`) for trusted, per-execution `DSH_*` variables. The namespace is rebuilt for every model shell call: ambient `DSH_*` values are discarded by the executor, then the registry's current snapshot is injected. Built-in shell facts remain owned by the registry itself while plugins can register additional, enumerable facts with effect-scoped disposal. - -```ts cordis-catalog -/** - * Register one environment contributor. Names and keys are unique; built-in - * keys are reserved. Registration is disposed with the calling plugin fiber. - * @param contributor - declared key ownership and per-execution resolver. - * @returns the disposer that unregisters the contribution. - */ -register(contributor: BashEnvContributor): () => void - -/** - * Build the trusted `DSH_*` snapshot for one shell tool execution. - * @param execution - the current tool execution. - * @returns an immutable environment overlay containing built-ins and current contributions. - */ -collect(execution: ToolExecution): DshEnvironment - -/** - * Enumerate plugin-contributed variables without executing their resolvers. - * @returns declarations sorted by environment variable name. - */ -list(): BashEnvVariableInfo[] -``` - -Types: [DshEnvironment](../subsystems/subprocess.md) · [ToolExecution](../subsystems/tools.md) - -Source: [`packages/bash/bash-env/src/index.ts:89`](../../packages/bash/bash-env/src/index.ts) - -## `ctx.clientModuleHost` — `ClientModuleHostService` - -The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). - -```ts cordis-catalog -/** - * Current composed entry graph (stable object between changes). - * @returns the graph served as `window.__DSH_BOOT__`. - */ -graph(): WebBootGraph - -/** - * Absolute path of an entry's client bundle. - * @param id - entry id (package name). - * @returns the path, or undefined for an unknown id. - */ -clientPath(id: string): string | undefined - -/** - * Re-hash one bundle (the HMR watch's registration hook — the only entry - * point through which bundle content changes reach the graph). - * @param id - entry id (package name). - * @returns the new rev, or undefined for an unknown id. - */ -rebuilt(id: string): string | undefined - -/** - * Subscribe to bundle rebuilds; fires only when the re-hash changed the rev. - * @param listener - receives the entry id and its new bundle rev. - * @returns the unsubscriber. - */ -onRebuilt(listener: (id: string, rev: string) => void): () => void - -/** - * Fires after any flush that recomposed the graph (row added/removed, or a - * rebuilt rev change). Pull model: listeners re-read {@link graph}. - * @param listener - notified with no payload. - * @returns the unsubscriber. - */ -onGraphChanged(listener: () => void): () => void -``` - -Types: [WebBootGraph](../subsystems/client-modules.md) - -Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts) - -## `ctx.codeRuntime` — `CodeRuntime` (abstract seam) - -Registers one `ctx.codeRuntime` implementation. Program, budget, abort, and substrate failures resolve in CodeRunResult; only seam misuse rejects. Implementations bridge structured-cloneable bindings, materialize each declared namespace rejection class, treat programs as hostile peers, isolate runs from one another, and terminate and await in-flight runs during disposal. - -```ts cordis-catalog -/** - * Execute one program against the request's bindings and capture what it - * emitted. See the class doc for the resolution contract (error is a result - * field; rejection means seam misuse only). - * @param request - the program, its bindings, and the abort signal; the - * request carries everything the runtime acts on, with no hidden defaults. - * @returns the run's outcome: completion value (when transferable), the - * ordered log capture, and the failure (if any). - */ -abstract run(request: CodeRunRequest): Promise -``` - -Types: [CodeRunRequest](../subsystems/code-runtime.md) · [CodeRunResult](../subsystems/code-runtime.md) - -Source: [`packages/code-runtime/code-runtime/src/index.ts:104`](../../packages/code-runtime/code-runtime/src/index.ts) - -## `ctx.commands` — `CommandService` - -Human-command registry. Plain-context definitions are global; definitions registered through a command-injected child of an agent context shadow globals for that agent. - -```ts cordis-catalog -/** - * Register a global or calling-agent-scoped command. - * @param definition - discovery metadata and direct UI handler. - * @returns the exact effect disposer that unregisters this definition. - */ -register(definition: CommandDefinition): () => void - -/** - * List the effective immutable command descriptors for one agent. - * @param agent - exact receiving agent and scoped-layer key. - * @returns name-sorted descriptors after scoped shadowing. - */ -list(agent: Agent): readonly CommandDescriptor[] - -/** - * Resolve one effective command definition. - * @param agent - exact receiving agent and scoped-layer key. - * @param name - command name without a slash. - * @returns the scoped shadow or global definition. - */ -find(agent: Agent, name: string): CommandDefinition | undefined - -/** - * Parse and execute a known command without sending it to the model. - * - * A resolved command's lifecycle is logged: `command/run` is appended - * before the handler is invoked and `command/done` after settlement (a - * thrown or aborted handler settles as `kind: 'error'`). Both are direct - * log-only appends — no turn wraps them, and persistence drains them at - * ordinary checkpoints. Admission misses (syntax or unknown name) log - * nothing — they never entered a handler. A `command/run` append failure - * fails the execution loud; a `command/done` append failure on the - * handler-failure path is contained so the handler's own error stays the - * reported failure. - * - * @param agent - exact receiving agent. - * @param line - complete slash-command line. - * @param signal - cancellation signal owned by the UI request. - * @returns the settled execution (result + lifecycle pairing id), or - * `undefined` when syntax or name does not resolve. - */ -async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise -``` - -Types: [Agent](../subsystems/core.md) · [CommandDefinition](../subsystems/commands.md) · [CommandDescriptor](../subsystems/commands.md) - -Source: [`packages/interaction/commands/src/index.ts:305`](../../packages/interaction/commands/src/index.ts) - -## `ctx.compact` — `CompactService` (abstract seam) - -Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. The replacement user message uses COMPACT_CHECKPOINT_SOURCE so consumers recognize it independently of the backend. Load one implementation per context as `ctx.compact`. - -```ts cordis-catalog -/** - * Consider automatic compaction for one explicit trigger. Pressure policy - * uses the latest durable routed request, while context-overflow policy may - * force a useful balanced reduction even below the normal threshold. Return - * `null` when no safe range can be compacted. A single oversized retained - * unit or request envelope cannot be repaired through surface compaction. - * - * @param agent - agent context owning the session surface and routing options. - * @param trigger - normal pressure or provider-confirmed context overflow. - * @param signal - cancellation signal; model-backed implementations must forward it. - * @returns the compaction result, or `null` if no compaction was needed. - */ -abstract compactIfNeeded( agent: CompactAgentContext, trigger: CompactionTrigger, signal: AbortSignal, ): Promise - -/** - * Explicitly compact useful history even below automatic pressure thresholds. - * Implementations synchronously start an idle task before any asynchronous - * work, select a useful range without writing on a no-op, then - * append a standalone `compact/start` before summarization. That durable - * marker is the compaction lock until one `compact/end` attempt. Later waking - * prompts remain accepted in FIFO order and start only after the optional - * durability checkpoint and idle-task settlement. Context injected while the - * summary runs may sit between the marker pair; only the selected span must - * remain stable. - * - * @param agent - idle agent whose durable history should be compacted. - * @param signal - cancellation scoped to this compaction request. - * @returns the compaction result, or `null` when no safe useful range exists. - * @throws {@link ManualCompactionError} for expected busy, agent-cancellation, - * changed-span, summarization/shrink, commit-stage, or persistence failures; - * an aborted request preserves its exact abort reason. Failed attempts remain - * visible in the log. - */ -abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, ): Promise - -/** - * Forcibly compact a range of surface nodes into a single summary node. - * `start` and `end` name an inclusive span by surface position, not numeric seq - * order; replacements can make visible seqs non-monotonic. Both edges must be - * balanced so assistant tool calls remain paired with their results. A model- - * backed implementation forwards cancellation and rejects active, missing, - * reversed, or unbalanced ranges. The target session is `agent.session`. - * Its replacement user message must use {@link COMPACT_CHECKPOINT_SOURCE}. - * Use {@link toolPairingBalancedBefore} and {@link toolPairingBalancedAfter} - * for the edge checks. - * - * @param start - first surface seq, inclusive. - * @param end - last surface seq, inclusive. - * @param agent - context whose session is mutated and whose routing options guide summarization. - * @param signal - optional cancellation; model-backed implementations must forward it. - * @throws when compaction is active or the range is missing, reversed, or unbalanced. - * @returns the appended event seqs, summary, replaced range, and token accounting. - */ -abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise -``` - -Types: [CompactionResult](../subsystems/compaction.md) · [CompactionTrigger](../subsystems/compaction.md) - -Source: [`packages/compact/compact/src/index.ts:93`](../../packages/compact/compact/src/index.ts) - -## `ctx.credentials` — `Credentials` (abstract seam) - -Abstract credential service. Providers implement the four operations over their source layers; one seam-wide rule binds them all: an empty stored value is absent everywhere — `resolve` skips it, `describe` reports it unconfigured — so a blank never masquerades as a configured secret. - -```ts cordis-catalog -/** - * Resolve one reference to its current value. Resolution is per call: - * consumers re-resolve at each operation and must not cache across - * operations — that per-operation read is what makes a changed credential - * reach the next operation without a restart. - * @param ref - the reference to resolve. - * @returns the value and its source, or `undefined` while unconfigured. - */ -abstract resolve(ref: CredentialRef): Promise - -/** - * Describe one reference for configuration surfaces without exposing the - * value. - * @param ref - the reference to describe. - * @returns configured state, supplying source, and writability. - */ -abstract describe(ref: CredentialRef): Promise - -/** - * Durably store one value in the provider-managed writable source. Rejects - * while a read-only source shadows the reference — the write would appear - * to succeed while resolution keeps returning the shadowing value — and - * rejects an empty value (use {@link unset}). - * @param ref - the reference to store. - * @param value - the non-empty secret value. - */ -abstract set(ref: CredentialRef, value: string): Promise - -/** - * Remove one reference from the provider-managed writable source; removing - * an absent reference is a no-op. Rejects while a read-only source shadows - * the reference, like {@link set}. - * @param ref - the reference to remove. - */ -abstract unset(ref: CredentialRef): Promise -``` - -Types: [CredentialInfo](../subsystems/credentials.md) · [CredentialRef](../subsystems/credentials.md) · [ResolvedCredential](../subsystems/credentials.md) - -Source: [`packages/credentials/credentials/src/index.ts:77`](../../packages/credentials/credentials/src/index.ts) - -## `ctx.directoryPicker` — `DirectoryPicker` (abstract seam) - -Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls. - -```ts cordis-catalog -/** - * The backend's interaction capability. - * @returns the discriminated capability consumers switch on. - */ -abstract capability(): DirectoryPickerCapability -``` - -Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts) - -## `ctx.e2b` — `E2BSandboxService` - -Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation. - -```ts cordis-catalog -/** - * Return the shared live SDK handle. - * @returns the created sandbox after the configured cwd exists. - * @throws when E2B rejects creation or the service is disposing. - */ -async getSandbox(): Promise -``` - -Source: [`packages/e2b/e2b/src/index.ts:74`](../../packages/e2b/e2b/src/index.ts) - -## `ctx.fs` — `FileSystem` (abstract seam) - -Abstract filesystem provider. Targets must preserve identity across aliases; reads expose regular UTF-8 text or typed errors, listings are stable and content-free, and mutations are atomic. Optional guards add stale protection without changing the unguarded provider contract. - -```ts cordis-catalog -/** - * Resolve a model/plugin-supplied path into a stable {@link FsTarget}. May perform I/O (a - * remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence - * async even though the local backend only normalizes + realpaths. - * - * @param path - the path to resolve; relative paths resolve against `opts.cwd`. - * @param opts - optional cwd override and cancellation signal. - * @returns the stable target; the same file yields the same `targetKey`. - */ -abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise - -/** - * Return the canonical absolute path a subprocess in this filesystem's - * execution world can open. The path is deliberately separate from - * {@link FsTarget.targetKey}: consumers may pass this value to another OS - * capability, but must continue treating the target key as opaque. - * @param target - the resolved target whose process path is required. - * @returns an absolute path in the backend's execution world. - */ -abstract processPath(target: FsTarget): string - -/** - * Return the canonical `file:` URI for a target in this filesystem's - * execution world. Backends own URI encoding because the host platform may - * differ from the execution platform. - * @param target - the resolved target to encode. - * @returns the target's canonical file URI. - */ -abstract fileUrl(target: FsTarget): string - -/** - * Test canonical containment without exposing or parsing backend target - * keys. Both targets must come from this provider. - * @param parent - canonical directory target. - * @param child - canonical candidate target. - * @returns true when `child` is `parent` or a descendant of it. - */ -abstract contains(parent: FsTarget, child: FsTarget): boolean - -/** - * Return target metadata, or `undefined` when the target does not exist. - * @param target - the resolved target to stat. - * @param signal - aborts the metadata round-trip. - * @returns metadata only, never content; undefined for an absent target. - */ -abstract stat(target: FsTarget, signal?: AbortSignal): Promise - -/** - * Return path metadata without following the final path component when it is a - * symbolic link. This is intentionally path-shaped, not target-shaped: - * {@link resolve} follows symlinks to produce the stable identity used by - * normal reads/writes, while `lstat` lets a consumer reject the path itself - * before that follow happens. - * - * `opts.cwd` follows {@link resolve}'s cwd rules. `undefined` means the path is - * absent. - * @param path - the path to inspect; relative paths resolve against `opts.cwd`. - * @param opts - `cwd` overrides the backend's default base for relative paths. - * @param signal - aborts the metadata round-trip. - * @returns metadata only, never content; undefined for an absent path. - */ -abstract lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise - -/** - * Read the whole regular text file as a single decoded string. - * @param target - the resolved target to read. - * @param signal - aborts the read. - * @returns the full decoded UTF-8 content. - */ -abstract readText(target: FsTarget, signal?: AbortSignal): Promise - -/** - * Stream the whole regular text file as decoded text chunks (same text - * semantics as {@link readText}, for large files). The backend owns - * cross-chunk UTF-8 decoding and binary rejection so the policy layer never - * touches raw bytes. - * @param target - the resolved target to read. - * @param signal - aborts the stream, including between chunks. - * @returns the chunk iterable, decoded and validated like {@link readText}. - */ -abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> - -/** - * List direct children of a directory in stable name order. Returns resolved - * child targets plus cheap metadata only; never reads file contents. - * @param target - the resolved directory target. - * @param signal - aborts the listing. - * @returns one entry per direct child, in stable name order. - */ -abstract listDir(target: FsTarget, signal?: AbortSignal): Promise - -/** - * Atomically create or replace UTF-8 text. `expected` guards intent and - * staleness; omission allows unconditional overwrite. - * @param target - the resolved target to write. - * @param content - the full new file content. - * @param expected - the write intent guarding the write; omit for unconditional. - * @param signal - aborts before the atomic rename takes effect. - * @param sandboxPolicy - the per-call mode and workspace root this write - * runs under; a sandboxing backend fences the write by it, the bare backend - * ignores it. Omit to leave the backend its own default. - * @returns the outcome, including the version the write produced. - */ -abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise - -/** - * Atomically edit literal text. When supplied, the version guard is checked - * before matching so stale content reports `FS_STALE_VERSION`; omission edits - * the current content without a freshness precondition. - * @param target - the resolved target to edit. - * @param edit - the literal search/replace request. - * @param expected - the version guard; omit for an unconditional edit. - * @param signal - aborts before the atomic rename takes effect. - * @param sandboxPolicy - the per-call mode and workspace root this edit runs - * under; a sandboxing backend fences the edit by it, the bare backend - * ignores it. Omit to leave the backend its own default. - * @returns the outcome, including the version the edit produced. - */ -abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise -``` - -Types: [FsDirEntry](../subsystems/filesystem.md) · [FsEditOutcome](../subsystems/filesystem.md) · [FsEditRequest](../subsystems/filesystem.md) · [FsInfo](../subsystems/filesystem.md) · [FsPathInfo](../subsystems/filesystem.md) · [FsTarget](../subsystems/filesystem.md) · [FsVersion](../subsystems/filesystem.md) · [FsWriteIntent](../subsystems/filesystem.md) · [FsWriteOutcome](../subsystems/filesystem.md) · [SandboxExecutionPolicy](../subsystems/sandbox.md) - -Source: [`packages/fs/fs/src/index.ts:83`](../../packages/fs/fs/src/index.ts) - -## `ctx.goals` — `GoalService` - -Goal service (`ctx.goals`) backed exclusively by the owning session log. - -```ts cordis-catalog -/** - * Read the current goal for one exact live agent. - * @param agent - owning live agent. - * @returns a fresh view or `undefined` when no goal is current. - * @throws {@link GoalError} when the agent is not the registry's live instance. - */ -get(agent: Agent): GoalView | undefined - -/** - * Remove process-local continuation authority without changing durable goal - * phase or revision. Lifecycle owners use this before unloading a driver; - * a later human-authorized {@link resume} records the new activation edge. - * @param agent - owning live agent. - * @returns a fresh disarmed view, or `undefined` when no goal is current. - */ -disarm(agent: Agent): GoalView | undefined - -/** - * Create and arm a goal. A completed goal may be replaced; every other - * current phase must be cleared or resumed instead. - * @param agent - owning live agent. - * @param request - objective and optional round cap. - * @returns the created live view. - */ -create(agent: Agent, request: CreateGoalRequest): GoalView - -/** - * Edit objective and/or round cap without changing phase. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @param request - at least one replacement field. - * @returns the edited view. - */ -@Remote('edit') edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView - -/** - * Pause an active goal and disarm automatic continuation. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @returns the paused view. - */ -@Remote('pause') pause(agent: Agent, ref: GoalRef): GoalView - -/** - * Resume and arm a stopped goal, or rearm an active goal after a - * session-start edge, while its round budget still has capacity. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @returns the active view. - */ -@Remote('resume') resume(agent: Agent, ref: GoalRef): GoalView - -/** - * Mark a current non-complete goal complete and disarm it. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @returns the completed view. - */ -@Remote('complete') complete(agent: Agent, ref: GoalRef): GoalView - -/** - * Mark an active goal blocked and disarm it. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @param reason - policy-owned stable code and human-readable explanation. - * @returns the blocked view with its durable reason. - */ -block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView - -/** - * Clear the current goal while retaining a durable tombstone and history. - * @param agent - owning live agent. - * @param ref - expected current revision. - * @returns the tombstone ref whose revision is one past the cleared snapshot. - */ -@Remote('clear') clear(agent: Agent, ref: GoalRef): GoalRef - -/** - * Create one Goal through the remote boundary. - * @param agent - exact live Agent resolved from the wire identity. - * @param request - objective and optional round cap. - * @returns the created Goal identity. - */ -@Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult -``` - -Types: [Agent](../subsystems/core.md) · [CreateGoalRequest](../subsystems/goal.md) · [CreateGoalResult](../subsystems/goal.md) · [EditGoalRequest](../subsystems/goal.md) · [GoalBlockReason](../subsystems/goal.md) · [GoalRef](../subsystems/goal.md) · [GoalView](../subsystems/goal.md) - -Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts) - -## `ctx.httpServer` — `HttpServerService` - -The web-shape HTTP carrier service. Activation listens immediately (route registration order carries no request-facing semantics: named routes are composed to be disjoint, and the fallback seat answers anything not yet claimed during the boot window — 404 until its owner registers). A listen failure throws out of init — a FAILED fiber the boot's fail-loud sweep reports. - -```ts cordis-catalog -/** - * Register a named route. Duplicate (kind, path) throws — route patterns are - * a composition-level contract, so a collision is a misconfiguration. - * @param route - kind, path, and the owning handler. - * @returns the disposer removing the route. - */ -register(route: WebRoute): () => void - -/** - * Register an exact-path HTTP upgrade route. Duplicate paths throw because - * one socket can have only one protocol owner. - * @param route - pathname and handler owning negotiation plus socket use. - * @returns the disposer removing the route. - */ -registerUpgrade(route: WebUpgradeRoute): () => void - -/** - * Claim the fallback seat: the handler answering every request no named - * route matches (the SPA dist server in the shipped Web composition). One - * owner only — a second registration throws, because two fallbacks cannot - * compose. - * @param handler - owns the full response lifecycle of unmatched requests. - * @returns the disposer releasing the seat. - */ -registerFallback(handler: WebRoute['handler']): () => void - -/** - * Register an index.html transform, applied by the fallback owner to every - * index response ({@link applyIndexTaps}) in registration order. - * @param transform - pure html-to-html function. - * @returns the disposer removing the transform. - */ -tapIndex(transform: (html: string) => string): () => void - -/** - * Run an index.html body through the registered taps in registration order - * — called by the fallback owner on every index response it renders. - * @param html - the raw index.html body. - * @returns the transformed body. - */ -applyIndexTaps(html: string): string -``` - -Types: [WebRoute](../subsystems/http-server.md) - -Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts) - -## `ctx.invariants` — `InvariantService` - -Package-owned invariant registry with global and regex-based selection. - -```ts cordis-catalog -/** - * Register one package's invariant installer. The package name is reserved - * even when filtering disables its checks. Enabled installers run in a child - * fiber; failure disposes that fiber and releases the reservation. - * @param packageName - full npm package name that owns the contribution. - * @param installer - listener or startup-check installer for the child context. - * @returns an effect-scoped disposer for the registration. - */ -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) - -## `ctx.llm` — `LlmService` - -The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. - -```ts cordis-catalog -/** - * Register an adapter for the given provider routes. Throws `LlmError` with code - * `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing). - * Disposed with the fiber. - * @param providers - every provider route this adapter should serve. - * @param adapter - the adapter that streams calls for those providers. - * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}. - */ -registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle - -/** - * Describe provider routes with a registered adapter. - * @returns detached provider metadata in registration order. - */ -listProviders(): LlmProviderInfo[] - -/** - * Declare provider routes an adapter plugin can activate through - * configuration. Registration is all-or-nothing: an empty list, invalid - * entry, or a provider already declared by any registration throws - * `LlmError` without registering the rest. Disposed with the fiber. - * @param entries - every configurable provider this plugin owns. - * @returns a handle that withdraws all of them, and can atomically replace them. - */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle - -/** - * List every declared configurable provider, registered or dormant. - * @returns detached directory entries in declaration order. - */ -listConfigurableProviders(): LlmConfigurableProvider[] - -/** - * Offer to interrogate provider endpoints on behalf of the settings - * namespace this plugin owns. The namespace is the key because that is what - * a configuration surface already holds from the configurable-provider - * directory, and because a provider being *added* has no route to name yet. - * Disposed with the fiber. - * @param settingsNs - the namespace whose profiles this discovery serves. - * @param discover - interrogates one endpoint; must honor `request.signal`. - * @returns the disposer that withdraws the offer. - */ -registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void - -/** - * Interrogate one provider endpoint for the models it advertises. The - * request describes a draft, not a stored route, so nothing here reads or - * writes settings or credentials — the caller owns both, and the reply is - * candidate metadata a surface may offer for adoption. - * @param settingsNs - namespace whose registered discovery serves this draft. - * @param request - the endpoint, protocol, and one-shot credential to use. - * @returns the advertised models, deduplicated in endpoint order. - */ -async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise - -/** - * Resolve the retry policy captured when one provider route was registered. - * @param provider - registered provider route to inspect. - * @returns the provider-owned policy, with normal defaults already resolved. - */ -providerRetryPolicy(provider: string): ResolvedRetryPolicy - -/** - * Discover models advertised by one registered provider. Catalog membership - * is advisory and never changes routing or request validation. - * @param provider - registered provider route to inspect. - * @returns detached model metadata in adapter-preferred order. - */ -async listModels(provider: string): Promise - -/** - * Resolve and validate all metadata from the adapter that owns one exact - * route. The result is detached from adapter-owned objects; catalog - * membership remains advisory and does not control request routing. - * @param provider - registered provider route to inspect. - * @param model - exact model id passed to the adapter. - * @param signal - optional cancellation for adapter-owned asynchronous lookup. - * @returns exact model identity plus available context and reasoning metadata. - */ -async resolveModelInfo( provider: string, model: string, signal?: AbortSignal, ): Promise - -/** - * Validate a conversation call config against its exact model capability and - * materialize adapter-configured defaults. Unsupported explicit efforts - * reject before provider I/O; no clamping or aliasing is performed. This - * standalone query does not bind a later dispatch; use {@link prepareCall} - * when logging and streaming must share one adapter registration. - * @param config - provider/model route and optional request controls. - * @param signal - optional cancellation for adapter-owned capability lookup. - * @returns a detached config only when a default must be materialized. - */ -async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise - -/** - * Resolve one call under its current adapter registration. The returned - * one-shot handle keeps that registration across header logging and dispatch, - * so HMR cannot combine one adapter's capability result with another adapter. - * @param config - provider/model route and optional request controls. - * @param signal - optional cancellation for adapter-owned capability lookup. - * @returns a prepared config and its registration-bound stream entry point. - */ -async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise - -/** - * Stream one model call as raw chunks (token-level deltas). Replay state is - * retained only when the same adapter instance owns its historical provider - * and the target provider. Final adapter selection remains fixed through - * asynchronous exact-model resolution and dispatch. Adapter selection, - * dispatch, and iteration failures become terminal `error` or `aborted` - * finish chunks; middleware, nested-call, cleanup, and consumer failures - * remain thrown. - * @param options - the full request; `options.provider` selects the adapter. - * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. - */ -stream(options: GenerateOptions): AsyncIterable -``` - -Types: [AdapterRegistrationHandle](../subsystems/core.md) · [DirectoryRegistrationHandle](../subsystems/core.md) · [GenerateOptions](../subsystems/core.md) · [LlmAdapter](../subsystems/llm-streaming.md) · [LlmCallConfig](../subsystems/core.md) · [LlmConfigurableProvider](../subsystems/core.md) · [LlmDiscoveredModel](../subsystems/core.md) · [LlmModelDiscoveryRequest](../subsystems/core.md) · [LlmModelInfo](../subsystems/core.md) · [LlmProviderInfo](../subsystems/core.md) · [LlmResolvedModelInfo](../subsystems/core.md) · [PreparedLlmCall](../subsystems/llm-streaming.md) · [ResolvedRetryPolicy](../subsystems/llm-streaming.md) · [StreamChunk](../subsystems/llm-streaming.md) - -Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) - -## `ctx.permission` — `PermissionService` - -Owns the deployment's permission presets and their write path. Requires a confining `ctx.bash` executor and `ctx.approval`; unmatched knob values are reported as CUSTOM_PRESET, not an error. - -```ts cordis-catalog -/** - * Resolve the preset matching the effective knob values. A still-matching - * last selection wins shared-bundle ties; otherwise the first table match - * wins, or {@link CUSTOM_PRESET} when no entry matches. - * @param events - the session's events in log order. - * @returns the effective preset name, or `custom` when nothing matches. - */ -current(events: readonly SessionEvent[]): string - -/** - * Build the whole select value for one folded knob state: every table - * option in declaration order, `custom` appended exactly while derived. - * @param state - the folded knob overrides. - * @returns the `permissions` projection payload. - */ -selectFor(state: KnobState): PermissionSelect - -/** - * Resolve a preset's knob bundle. - * @param name - the preset name to resolve. - * @returns the configured bundle. - * @throws when `name` is not in the table. - */ -resolve(name: string): PresetSpec - -/** - * Build the client option for a table entry or {@link CUSTOM_PRESET}. A - * missing label falls back to the table key. - * @param name - a table key, or `custom`. - * @returns the option a client renders. - * @throws when `name` is neither a table key nor `custom`. - */ -optionOf(name: string): PresetOption - -/** - * Record a changed preset, then update each changed knob through its own - * setter. Selecting the effective preset again appends nothing. - * @param session - the session the switch belongs to. - * @param name - the preset to switch to; unknown names throw. - */ -set(session: Session, name: string): void -``` - -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) - -## `ctx.planMode` — `PlanModeService` - -`ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror. - -```ts cordis-catalog -/** - * Read the logged plan state and any selected state awaiting a boundary. - * - * @param agent The agent to read. - * @returns Current logged state plus a pending selection, when present. - */ -get(agent: Agent): { active: boolean; pending?: boolean } - -/** - * Select whether plan mode should be active. Between turns the change - * commits immediately — no request boundary would arrive until the next - * prompt, so a queued intent would hang (the open-turn fold is the idle - * signal: agent status stays `running` through post-turn checkpointing, - * where a boundary equally never comes). During an open turn the - * selection is held as pending intent for the next in-turn request - * boundary. Repeated selection of the current or already-pending state is - * a no-op. - * - * @param agent The agent to switch. - * @param active Whether plan mode should be active. - * @returns what happened: `committed` (logged now), `queued` (awaiting the - * next boundary), `cancelled` (an opposite pending selection was cleared; - * the logged state already matches), or `noop` (already in that state). - */ -set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' -``` - -Types: [Agent](../subsystems/core.md) - -Source: [`packages/plan/plan-mode/src/index.ts:183`](../../packages/plan/plan-mode/src/index.ts) - -## `ctx.pty` — `PtyService` - -In-process registry for replaceable PTY backends and exact-Agent sessions. - -```ts cordis-catalog -/** - * Register one backend type for this effect scope. - * @param backend - provider with a non-empty unique type. - * @returns disposer that removes exactly this contribution. - */ -registerBackend(backend: PtyBackend): () => void - -/** - * List registered backend types in registration order. - * @returns fresh backend type names. - */ -listBackends(): string[] - -/** - * Create and publish one owner-scoped session after backend setup succeeds. - * @param owner - exact registered Agent that owns access and cleanup. - * @param request - backend type plus optional owner-local name and cwd. - * @param signal - cancellation of unpublished setup. - * @returns published identity, metadata, status, and MOTD. - */ -async spawn(owner: Agent, request: PtySpawnRequest, signal?: AbortSignal): Promise - -/** - * Test whether an exact owner has a published session or unpublished spawn. - * @param owner - exact live owner to inspect. - * @returns true across the entire spawn-to-close interval, with no publication gap. - */ -hasOwnerActivity(owner: Agent): boolean - -/** - * Start one exclusive interactive send. - * @param owner - exact session owner. - * @param id - target PTY identity. - * @param request - explicit text, submit behavior, and cancellation. - * @returns live operation handle for foreground await or task registration. - */ -startSend(owner: Agent, id: PtySessionId, request: PtySendRequest): PtySendOperation - -/** - * Read one bounded scrollback page from an owned session. - * @param owner - exact session owner. - * @param id - target PTY identity. - * @param request - optional newest-relative offset and line count. - * @returns bounded retained text and pagination metadata. - */ -read(owner: Agent, id: PtySessionId, request: PtyReadRequest = {}): PtyReadResult - -/** - * Deliver an allowed signal through an owned backend session. - * @param owner - exact session owner. - * @param id - target PTY identity. - * @param signal - allowed POSIX signal name. - * @returns delivered foreground process-group identity. - */ -signal(owner: Agent, id: PtySessionId, signal: PtySignal): Promise - -/** - * Close one owned session and remove it only after quiescent backend cleanup. - * @param owner - exact session owner. - * @param id - target PTY identity. - * @param reason - diagnostic cleanup reason. - * @returns true for a newly closed session, false when the same close is already in flight. - */ -async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise - -/** - * List fresh snapshots for exactly one owner. - * @param owner - exact owner whose sessions are visible. - * @returns owner-visible snapshots in publication order. - */ -list(owner: Agent): PtySessionSnapshot[] -``` - -Types: [Agent](../subsystems/core.md) · [PtyBackend](../subsystems/pty.md) · [PtyReadRequest](../subsystems/pty.md) · [PtyReadResult](../subsystems/pty.md) · [PtySendOperation](../subsystems/pty.md) · [PtySendRequest](../subsystems/pty.md) · [PtySessionId](../subsystems/pty.md) · [PtySessionSnapshot](../subsystems/pty.md) · [PtySignal](../subsystems/pty.md) · [PtySignalResult](../subsystems/pty.md) · [PtySpawnRequest](../subsystems/pty.md) · [PtySpawnResult](../subsystems/pty.md) - -Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts) - -## `ctx.sandbox` — `SandboxProvider` (abstract seam) - -Abstract process-sandbox service. confine must return enforcing argv or fail closed at wrap or runner-execution time; silent unconfined passthrough is forbidden. Functional probes arbitrate multi-runner chains and may be skipped for a sole candidate, whose own refusal remains the fail-closed end. - -```ts cordis-catalog -/** - * Wrap `argv` so it executes confined under `policy` on this host; the - * caller spawns the returned argv in place of its own. - * @param argv - the exact argv the caller is about to spawn (program plus - * arguments), NOT a shell string — a shell-shaped consumer passes - * `['bash', '-c', command]`. - * @param policy - the file-effect policy this execution runs under, - * carried per call (see {@link SandboxPolicy}). - * @returns the argv to spawn instead, plus the enforcement completeness - * the selected backend achieves for it. - */ -abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv -``` - -Types: [ConfinedArgv](../subsystems/sandbox.md) · [SandboxPolicy](../subsystems/sandbox.md) - -Source: [`packages/sandbox/sandbox/src/index.ts:148`](../../packages/sandbox/sandbox/src/index.ts) - -## `ctx.sandboxPolicy` — `SandboxPolicyService` - -The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. - -```ts cordis-catalog -/** - * Resolve the complete policy for one capability call. An approved explicit - * mode outranks the session's last `sandbox/mode` event, which outranks the - * deployment default. A session cwd is its workspace-write boundary; the - * configured root is the fallback for agentless calls and sessions without a - * cwd. - * @param request - optional session and approved mode override. - * @returns the fully resolved per-call mode and absolute workspace root. - */ -resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy - -/** - * Read the session override without applying the deployment default. - * @param session - session whose log supplies the override. - * @returns the last logged mode, or `undefined` without one. - */ -overrideOf(session: Session): SandboxMode | undefined -``` - -Types: [SandboxExecutionPolicy](../subsystems/sandbox.md) · [SandboxMode](../subsystems/sandbox.md) · [SandboxPolicyRequest](../subsystems/sandbox.md) · [Session](../subsystems/session.md) - -Source: [`packages/sandbox/sandbox-policy/src/index.ts:91`](../../packages/sandbox/sandbox-policy/src/index.ts) - -## `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) - -Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events. - -```ts cordis-catalog -/** - * Resolve this backend's independent local artifact for a session without - * reading, creating, flushing, or otherwise materializing it. Backends such - * as SQLite that do not own one artifact per session return `undefined`. - * @param meta - the immutable session header whose artifact is requested. - * @returns the backend-specific absolute location, when one exists. - */ -abstract locate(meta: SessionHeader): SessionLocation | undefined - -/** - * Register a new session's metadata. A backend MAY defer the physical write - * until the first {@link append} (lazy materialization), in which case a - * created-but-never-appended session is absent from {@link list} - * — abandoned sessions leave nothing behind. - * @param meta - the immutable header (id, version, cwd, lineage) to record. - */ -abstract create(meta: SessionHeader): Promise - -/** - * Durably persist a batch of events. Honors the append-only and contiguous- - * seq contracts: the first event's `seq` MUST equal the stored next-seq - * (after `load` has durably closed any interrupted turn). Rejects non-JSON- - * serializable `event.data` with an error naming the offending event type. - * @param id - the session the batch belongs to. - * @param events - the contiguous batch to persist, in seq order. - */ -abstract append(id: SessionId, events: readonly SessionEvent[]): Promise - -/** - * Prepare the exact unpublished Session used by resume. Implementations may - * reuse object graphs retained by an earlier {@link inspect} after confirming - * their durable revision is still current; disposal releases an unpublished - * reservation. Revision retries require the durable log to remain unchanged - * for one read/check round trip; continuous external writers may delay completion. - * @param id - persisted session to prepare. - * @param signal - optional cancellation for preparation work. - * @returns one owned unpublished Session preparation. - */ -async prepare(id: SessionId, signal?: AbortSignal): Promise - -/** - * Load an immutable balanced logical view and commit any required cold - * recovery. A complete interrupted final turn is preserved and durably - * closed with missing tool errors plus any open step and turn boundaries; - * only a torn final record is discarded. Unknown versions and corruption in - * the committed prefix reject. Implementations MUST NOT crash-repair an - * identity still bound to a live Session: a balanced live log may return as a - * durable snapshot, while an open live turn rejects. Returned values may be - * shared with immutable live or prepared state and must not be mutated. - * Revision-based implementations may wait for one stable read/check round trip. - * @param id - the persisted session to reload. - * @returns the header and a log ending on a balanced `turn/end`. - */ -abstract load(id: SessionId): Promise - -/** - * Inspect an immutable logical session without committing recovery or - * publishing it. A cold complete interrupted turn receives synthetic closers - * in memory and a torn physical tail remains untouched. An already-live - * Session instead yields its current immutable snapshot, which may contain an - * open turn and its `session/end-seed` boundary. Coordinator-backed - * implementations retain the exact cold unpublished Session for bounded - * reuse by a later {@link prepare}. A stale ready source is reloaded; a source - * already committing or reserved for resume remains exclusive, and inspection - * may borrow its immutable view. Callers borrow only the immutable header and - * log. Continuous external writers may delay revision convergence. - * @param id - the persisted session to inspect. - * @param signal - optional cancellation for queued and backend read work. - * @returns the validated header and current logical event log. - */ -abstract inspect(id: SessionId, signal?: AbortSignal): Promise - -/** - * Read the stored events from `fromSeq` onward — the read-from-seq - * primitive for read models that resume from a watermark (e.g. a persisted - * projection cache folding only the tail past its checkpoint). Unlike - * {@link inspect}, it is a detached physical suffix read: no preparation - * cache, torn-tail truncation, synthetic closers, or coordinator-state - * publication. Only events from the valid contiguous stored prefix are - * returned, so a torn fragment never reaches the caller. `fromSeq` at or - * beyond the stored prefix returns an empty event list (never an error). - * Backends whose medium can seek by seq - * (SQLite) read only the suffix; sequential media (JSONL, both encodings) - * still parse the whole artifact and skip forward — the primitive bounds - * what is RETURNED and refolded, not every backend's physical read. - * @param id - the persisted session to read. - * @param fromSeq - first event seq to include; a non-negative safe integer. - * @param signal - optional cancellation for queued and backend read work. - * @returns the header and the stored events with `seq >= fromSeq`. - */ -abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> - -/** - * Lightweight listing from metadata, without a full-log parse. - * @param signal - optional cancellation for backend listing work. - * @returns one header per materialized session. - */ -abstract list(signal?: AbortSignal): Promise - -/** - * List materialized sessions with cheap per-log change tokens. - * - * Repeated observations of an unchanged log return the same revision. A - * successful mutating {@link load} repair changes the next listed revision. - * Revisions also distinguish independently backed stores so backend-local - * counters cannot compare equal across different persistence sources. - * @param signal - optional cancellation for backend snapshot-listing work. - * @returns one header and opaque revision per materialized session without loading full logs. - */ -abstract listSnapshots(signal?: AbortSignal): Promise -``` - -Types: [SessionEvent](../subsystems/core.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) · [SessionInspection](../subsystems/persistence.md) · [SessionLocation](../subsystems/persistence.md) · [SessionPersistenceSnapshot](../subsystems/persistence.md) · [SessionPreparation](../subsystems/persistence.md) - -Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) - -## `ctx.sessionProjectionCache` — `SessionProjectionCache` - -The persisted projection cache service. Opens the `session_projcache` domain at init, checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached row, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. - -```ts cordis-catalog -/** - * The zero-I/O listing read: whole values viewed straight from the stored - * rows (version-matching keys only), each cut carried with its watermark - * so a client value store can seed under its higher-seq-wins rule — as - * stale as the last durable checkpoint but never wrong, and never from an - * unrelated log (the caller's header is the identity witness). Fresher - * paths (the history tail baseline, {@link coldSnapshot}) supersede these - * values whenever a session is actually opened. - * @param meta - the listed session's header (identity witness; no log read). - * @returns the cut (`asOfSeq` = lowest served-row watermark), or - * `undefined` when no usable row exists for this lifecycle. - */ -cachedSnapshot(meta: SessionHeader): ProjectionSnapshot | undefined - -/** - * Durably checkpoint one live session NOW (both mandatory points call - * this; tests and carriers may too). The registry cut is snapshotted at - * this boundary (states are live references), then the whole record is - * replaced. NOT fail-soft — callers on the fail-soft paths contain it. - * @param session - the live session to checkpoint. - * @returns resolution after durability and event emission. - */ -async write(session: Session): Promise - -/** - * Cold-read one persisted session's projections with zero full-log load: - * cached rows + a persistence `readFrom` tail from the registry's restore - * floor, refolded by the registry and written back (fail-soft) so the next - * cold read starts closer. A cache row invalidated by a shrunk log - * (crash-repair truncation) triggers one full re-read from seq 0 — the - * ladder's slow rung, still no crash. Rejects when the session has no - * persisted log (`not found` from the persistence seam). - * @param id - the persisted session to read. - * @param signal - optional cancellation for the persistence reads. - * @returns the snapshot cut at the stored log end. - */ -async coldSnapshot(id: SessionId, signal?: AbortSignal): Promise -``` - -Types: [Session](../subsystems/session.md) · [SessionHeader](../subsystems/persistence.md) · [SessionId](../subsystems/core.md) - -Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../packages/session/session-projection-cache/src/index.ts) - -## `ctx.sessionProjections` — `SessionProjectionRegistry` - -`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. - -```ts cordis-catalog -/** - * Register one domain's unit. The registration is an effect on the calling - * context's fiber: disposing the fiber (or calling the returned disposer) - * removes the key — and the unit's cached cells — from subsequent drives - * and snapshots. - * @param definition - key, boundary schema, pure unit functions, and stateVersion. - * @returns the exact disposer that unregisters this unit. - */ -register(definition: ProjectionDefinition): () => void - -/** - * Subscribe to the change feed. The registration is an effect on the - * calling context's fiber. - * @param listener - called once per unit whose state reference changed, per committed event. - * @returns the exact disposer that unsubscribes. - */ -onChanged(listener: ProjectionChangeListener): () => void - -/** - * One consistent cut over every registered unit for one session, read from - * the watermark cache (missing cells fold lazily over the in-memory log). - * Fully synchronous — every value and `asOfSeq` reflect the same log - * position. Each value passes its unit's schema before leaving. - * @param session - the session whose projection values are read. - * @returns the snapshot; `values` is empty when no unit is registered. - */ -snapshot(session: Session): ProjectionSnapshot - -/** - * State-level checkpoint of every registered unit for one session, read - * from the watermark cache (missing cells fold lazily over the in-memory - * log). This is the write side of the persisted projection cache: the - * returned rows are the `(key → {ver, seq, val})` part of the durable - * `(sessionId, key, ver, seq, val)` - * rows. Every `val` is a DETACHED structured clone — never the live - * cell reference: the watermark cache is this registry's authoritative - * mutable state, and a caller reaching the live reference could corrupt - * every subsequent snapshot and frame through it (plain JSON by the unit - * contract, so the clone is total). - * @param session - the session whose unit states are checkpointed. - * @returns one row per registered key; empty when no unit is registered. - */ -checkpoint(session: Session): ProjectionCheckpoint - -/** - * The stored seq a {@link restore} tail read over `checkpoint` must start - * at: one event BELOW the lowest usable watermark (a row is usable when - * its `ver` matches the live unit's `stateVersion`; an absent or mismatched row - * pulls the floor to `0` — that key must refold the full log). The - * one-below anchor is load-bearing: the tail then proves how far the - * stored log still extends, so {@link restore} can detect a log that - * shrank below a row's watermark (crash-repair truncation) instead of - * serving the stale row as current — an empty tail read from the anchor - * yields an end below every watermark and the restore rejects for a full - * re-read. - * @param checkpoint - persisted rows for one session (possibly stale or empty). - * @returns the seq to hand the persistence `readFrom`, or `undefined` - * when no unit is registered (no read needed — {@link restore} would - * serve empty values regardless). - */ -restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined - -/** - * View a checkpoint's rows without any log read: for every registered - * unit whose row's `ver` matches, serve the schema-validated - * `view` of the stored state; mismatched or absent rows leave their key - * absent (a cold or listing consumer treats it as not-yet-available and a - * fuller read path refolds it). The zero-I/O rung of the read ladder — - * values are as stale as their rows, never wrong. - * @param checkpoint - persisted rows for one session (possibly stale or empty). - * @returns whole values per key with a usable row; empty when none. - */ -viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial - -/** - * Cold read: fold every registered unit over a stored log suffix, seeding - * each from its checkpoint row when usable — the one read recipe (cached - * state + forward tail replay + `view`) applied without a live `Session`. - * Call with the events returned by a persistence - * `readFrom(id, restoreFloor(checkpoint))` and that same floor as - * `baseSeq`; the floor's one-below anchor makes the supplied end honest, - * so a shrunk log is detected here. A row is usable iff its - * `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq` - * (`seq >= baseSeq - 1`), and it does not claim events past the - * supplied end (`seq <= endSeq`); an unusable row is discarded - * and its key refolds from `init` — which is only sound over the full - * log, so a discarded row with `baseSeq > 0` throws (the caller re-reads - * from seq 0, e.g. after a crash-repair truncation shrank the log below - * a row's watermark). - * @param checkpoint - persisted rows for one session (possibly stale or empty). - * @param events - the stored events with `seq >= baseSeq`, in seq order. - * @param baseSeq - the seq `events` starts at (its first event's seq when non-empty). - * @returns the snapshot cut at the supplied log end (`asOfSeq` is the last - * supplied event's seq, `baseSeq - 1` for an empty tail) plus the - * refreshed checkpoint rows at that cut, ready for a durable write-back. - */ -restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } -``` - -Types: [Session](../subsystems/session.md) · [SessionEvent](../subsystems/core.md) - -Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) - -## `ctx.sessionQuery` — `SessionQueryService` (abstract seam) - -Unified live-preferred session query service. - -Exact reads, filters, and traces are backend-independent concrete behavior. A backend implements full-text observation, reconciliation, ranking, cursor generations, and query execution on the same `ctx.sessionQuery` service. - -```ts cordis-catalog -/** - * Search the live-preferred logical corpus and group by session. - * @param request - query text, metadata filters, page size, and cursor. - * @param exec - optional cancellation control. - * @returns session hits ranked by their strongest matching event. - */ -abstract searchSessions( request: SessionSearchRequest, exec?: SessionSearchExecContext, ): Promise> - -/** - * Search events within one live-preferred logical session. - * @param request - target session, query text, filters, page size, and cursor. - * @param exec - optional cancellation control. - * @returns matching event hits and their target header from one indexed generation. - */ -abstract searchEvents( request: SessionEventSearchRequest, exec?: SessionSearchExecContext, ): Promise - -/** - * List the complete logical corpus using live-preferred records. - * @param signal - optional cancellation for persistence listing. - * @returns deterministic newest-first cloned session records. - */ -listSessions(signal?: AbortSignal): Promise - -/** - * Read and replay-validate one complete logical session log without making it live. - * @param sessionId - live or persisted session id to read. - * @returns cloned header and complete raw event log from one observation. - * @throws when persistence, header compatibility, or replay validation fails. - */ -async readSession(sessionId: SessionId): Promise - -/** - * Filter the complete logical corpus with provider-independent predicates. - * @param filters - ANDed session metadata and availability clauses. - * @param signal - optional cancellation for persistence listing. - * @returns matching cloned records in deterministic newest-first order. - */ -async filterSessions( filters: readonly SessionResultFilter[], signal?: AbortSignal, ): Promise - -/** - * Fold the latest log-backed title from one live-preferred logical session. - * @param sessionId - live or persisted session id to read. - * @param signal - optional cancellation for source resolution and title folding. - * @returns latest title snapshot, or `undefined` when the log has no title event. - */ -async readTitle( sessionId: SessionId, signal?: AbortSignal, ): Promise - -/** - * Fold the latest title and return its source header from one corpus observation. - * @param sessionId - live or persisted session id to read. - * @param signal - optional cancellation for source resolution and title folding. - * @returns cloned source header and optional latest title snapshot. - */ -async readTitleSnapshot( sessionId: SessionId, signal?: AbortSignal, ): Promise - -/** - * Fold titles for unique sessions from one cancellable corpus observation. - * - * Results preserve first-occurrence input order. Operational failures stay - * isolated per session, while cancellation rejects the complete operation. - * @param sessionIds - live or persisted session ids to observe. - * @param signal - optional cancellation shared by all source reads. - * @returns one fulfilled or rejected result per unique requested id. - */ -async readTitleSnapshots( sessionIds: readonly SessionId[], signal?: AbortSignal, ): Promise - -/** - * List lightweight raw-log event records for one logical session. - * @param sessionId - live-preferred session id to read. - * @returns event records in ascending seq order. - */ -async listEvents(sessionId: SessionId): Promise - -/** - * Scan first-party semantic event documents with provider-independent filters. - * @param sessionId - live-preferred session id to scan. - * @param filters - ANDed metadata and literal-text predicates. - * @returns matching semantic documents in ascending seq order. - */ -async filterEvents( sessionId: SessionId, filters: readonly SessionEventResultFilter[], ): Promise - -/** - * Read one session's complete current model surface from one corpus observation. - * @param sessionId - live-preferred session id to read. - * @returns cloned header, current surface, and raw-log capture boundary. - * @throws when source resolution fails or the session surface is invalid. - */ -async readSurface(sessionId: SessionId): Promise - -/** - * Trace known ancestry and descendants from one corpus observation. - * @param sessionId - logical session id to trace. - * @param signal - optional cancellation for persistence listing. - * @returns a complete lineage or an explicit unresolved parent boundary. - * @throws when corpus resolution fails, the target is absent, or its known ancestry cycles. - */ -async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise - -/** - * Trace one event's direct positional and provenance relationships. - * @param request - target session id and event seq. - * @param signal - optional cancellation for persisted source resolution. - * @returns source header, direct links, and the target's positional replacement chain. - * @throws when source resolution fails, the target is absent, or surface/provenance validation fails. - */ -async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise - -/** - * Read one full event plus a bounded raw-log context window. - * @param request - target session/seq and context sizes. - * @param signal - optional cancellation for persisted source resolution. - * @returns cloned target and neighboring events. - */ -async readEvent(request: SessionEventReadRequest, signal?: AbortSignal): Promise -``` - -Types: [SessionEventReadRequest](../subsystems/session-query.md) · [SessionEventRecord](../subsystems/session-query.md) · [SessionEventResultFilter](../subsystems/session-query.md) · [SessionEventSearchDocument](../subsystems/session-query.md) · [SessionEventSearchPage](../subsystems/session-query.md) · [SessionEventSearchRequest](../subsystems/session-query.md) · [SessionEventTraceObservation](../subsystems/session-query.md) · [SessionEventTraceRequest](../subsystems/session-query.md) · [SessionEventWindow](../subsystems/session-query.md) · [SessionId](../subsystems/core.md) · [SessionLineageTrace](../subsystems/session-query.md) · [SessionLogSnapshot](../subsystems/session-query.md) · [SessionRecord](../subsystems/session-query.md) · [SessionResultFilter](../subsystems/session-query.md) · [SessionSearchExecContext](../subsystems/session-query.md) · [SessionSearchHit](../subsystems/session-query.md) · [SessionSearchPage](../subsystems/session-query.md) · [SessionSearchRequest](../subsystems/session-query.md) · [SessionSurfaceSnapshot](../subsystems/session-query.md) · [SessionTitleObservation](../subsystems/session-query.md) · [SessionTitleObservationResult](../subsystems/session-query.md) · [SessionTitleSnapshot](../subsystems/session-title.md) - -Source: [`packages/session-query/session-query/src/index.ts:81`](../../packages/session-query/session-query/src/index.ts) - -## `ctx.sessionReferences` — `SessionReferenceService` - -Exact-read consumer that prepares immutable cross-session message context. - -```ts cordis-catalog -/** - * List reference candidates, ranked by working-directory affinity. - * @param agent - target agent; self is excluded and its cwd drives ranking. - * @param query - optional case-insensitive session-id/cwd/title substring. - * @param limit - optional positive result cap. - * @param signal - optional cancellation boundary for host autocomplete teardown. - * @returns candidates labeled by latest title or, when absent, session id. - */ -async listCandidates( agent: Agent, query: string = '', limit: number = this.config.candidateLimit, signal?: AbortSignal, ): Promise - -/** - * Snapshot all references before enqueue and return one aggregated durable context. - * @param agent - target agent; references to it are rejected. - * @param content - already host-normalized readable message content. - * @param references - structured source sessions in mention order. - * @param signal - optional cancellation boundary for host request teardown. - * @returns detached content and optional referenced-session context. - */ -async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise -``` - -Types: [Agent](../subsystems/core.md) · [ContentBlock](../subsystems/core.md) · [PreparedReferencedMessage](../subsystems/session-reference.md) · [SessionReferenceCandidate](../subsystems/session-reference.md) · [SessionReferenceInput](../subsystems/session-reference.md) - -Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) - -## `ctx.sessions` — `SessionStore` - -In-memory session store (`ctx.sessions`). - -Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. - -```ts cordis-catalog -/** - * Create a session owned by the calling fiber: disposing that fiber stops - * event notification and removes the session from the store. `options.seed` - * populates the session with a copy of those events (replay/fork); - * `options.meta` attaches creation metadata (validated absolute `cwd`, seed - * and parent lineage, and delegation depth) as the immutable - * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`). - * - * For an agent whose session must be torn down IN ORDER with its loop (so the - * loop's final events are published before the store attachment ends), do NOT use this - * — fold the session lifecycle into the agent's own effect via - * {@link prepare} + {@link enter} + {@link announce} (see - * `dsh-agent-loop`'s creation transaction). - * - * @param id - the session id; omitted, the store mints `session-`. - * @param options - seed events and/or creation metadata for the header. - * @returns the live session, already entered and announced. - * @throws if a session with `id` already exists, metadata is not a plain - * lossless-JSON record with valid scalar fields, or `meta.cwd` is a - * non-absolute path (storage backends key directories off it). - */ -create(id?: SessionId, options?: CreateSessionOptions): Session - -/** - * Build a session WITHOUT entering it into the store — validate the id/cwd and - * construct the {@link Session} (with its immutable {@link SessionHeader}). - * Pairs with {@link enter} + {@link announce}: a caller that owns a composite - * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE - * effect so a fiber unload tears the session + agent down as a single ORDERED - * chain rather than as racing sibling effects — which would remove the publication hooks - * before the driver's closing events commit, dropping them. - * - * @param id - the session id; omitted, the store mints `session-`. - * @param options - seed events and/or creation metadata for the header. With - * `seedSource: 'persistence'`, metadata and events must be fresh detached - * graphs whose ownership transfers to this call: they are validated and - * frozen in place through {@link Session.fromRestore}, so the caller must - * retain no mutable aliases. - * @returns the constructed session, NOT yet in the store. - * @throws if a session with `id` already exists, metadata is not a plain - * lossless-JSON record with valid scalar fields, or `meta.cwd` is a - * non-absolute path. - */ -prepare(id?: SessionId, options?: PrepareSessionOptions): Session - -/** - * Enter a {@link prepare}d session into the store: install the module-private - * append publication hooks and add it to the store. Returns the DETACH - * disposer (hooks + store removal). Does NOT emit `session/created` — - * the caller yields this disposer inside its effect and THEN calls - * {@link announce}, so a throwing `session/created` listener rolls the attach - * back instead of leaking it. - * - * Re-checks the id for a duplicate: `prepare` and `enter` are public - * cross-package primitives and a caller may interleave arbitrary work (or - * another create) between them, so a stale prepared session must NOT overwrite - * a live store entry of the same id — its detach disposer would later delete - * the REAL session. The {@link create} convenience and the agent factory call - * the two back-to-back so they never trip this, but the public seam cannot - * assume that. - * - * @param session - a {@link prepare}d session not yet in the store. - * @returns the detach disposer (publication hooks + store removal). When called from - * a synchronous `session/created` listener, removal and disposal wait until - * that creation dispatch unwinds. - * @throws if a session with this id is already in the store. - */ -enter(session: Session): () => void - -/** Emit `session/created` exactly once for an {@link enter}ed session (with - * the carrier {@link enter} captured). Separate from {@link enter} so the - * caller can yield the detach disposer first (rollback safety — see - * {@link enter}). - * @param session - the entered session to announce to listeners. - * @throws if the session is not live or its announcement already began, - * including a reentrant call from a creation listener. */ -announce(session: Session): void - -/** - * Dispatch the awaited `session/flush` durability checkpoint for `session`, - * with the carrier captured at {@link enter}. THE flush entry point: the - * store owns the carrier, so callers (the checkpoint policy's per-request - * barrier, goal-session's idle checkpoint, teardown drains, and consumers - * that flush themselves before reading storage) must come through here - * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, - * one spelling, and the scoped-dispatch invariant can pin it. - * @param session - the session whose buffered events must reach durable storage. - * @returns whether at least one durability listener participated, after every - * listener has settled successfully. - * @throws the first registered listener failure after every listener settles. - */ -async flush(session: Session): Promise - -/** - * Look up a live session. - * @param id - the session id to look up. - * @returns the session, or undefined when no live session has that id. - */ -get(id: SessionId): Session | undefined - -/** - * All live sessions, in creation order. - * @returns a fresh array; mutating it does not affect the store. - */ -list(): Session[] - -/** - * Create a live child session from a stable prefix of a live source. - * `boundary` is an inclusive source event seq; omitted means the source's - * current last event. The selected slice may end with a between-turn event - * but must not end inside an open turn. - * - * @param source - Live source session object or id. - * @param boundary - Inclusive source event seq to fork through; omitted means - * the source's current last event, and omitted on an empty source forks an - * empty child. - * @param childSessionId - Optional child session id; omitted delegates to - * `SessionStore`'s id policy. - * @returns The created live child session. - */ -fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session -``` - -Types: [CreateSessionOptions](../subsystems/persistence.md) · [PrepareSessionOptions](../subsystems/persistence.md) · [Session](../subsystems/session.md) · [SessionId](../subsystems/core.md) - -Source: [`packages/core/session/src/index.ts:807`](../../packages/core/session/src/index.ts) - -## `ctx.sessionTitle` — `SessionTitleService` - -Log-backed title fold plus asynchronous fallback generation. - -```ts cordis-catalog -/** - * Read the latest folded title from one live or replayed session. - * @param session - session whose log is the title source of truth. - * @returns latest title snapshot, or `undefined` before eligible input. - */ -get(session: Session): SessionTitleSnapshot | undefined - -/** - * Accept an explicit user title. Appends a `session/title` event with the - * `user` source, which pins the title: in-flight automatic generation is - * superseded and later user messages schedule none (an explicit - * {@link SessionTitleService.refresh} remains the deliberate unpin). - * @param session - exact live session to rename. - * @param title - raw user input; normalized before acceptance. - * @returns the accepted title snapshot. - * @throws {SessionTitleInvalidError} when the title normalizes to empty. - * @throws {Error} when the session is not live or the service is disposed. - */ -rename(session: Session, title: string): SessionTitleSnapshot - -/** - * Explicitly retry the registered provider, or materialize the built-in - * fallback when no provider is registered. - * @param session - exact live session to refresh. - * @param signal - optional caller cancellation. - * @returns latest accepted title, or `undefined` when no eligible text exists. - */ -async refresh(session: Session, signal?: AbortSignal): Promise - -/** - * Register the sole optional title provider. Disposal aborts its pending and - * active work before another provider may register. - * @param provider - provider identity, cadence, and generation function. - * @returns exact Cordis effect disposer, which settles after active calls quiesce. - */ -register(provider: SessionTitleProvider): () => Promise -``` - -Types: [Session](../subsystems/session.md) · [SessionTitleProvider](../subsystems/session-title.md) · [SessionTitleSnapshot](../subsystems/session-title.md) - -Source: [`packages/session/session-title/src/index.ts:261`](../../packages/session/session-title/src/index.ts) - -## `ctx.settings` — `Settings` (abstract seam) - -Abstract settings service. Providers implement raw-document storage (`load`/`persist`) and push external changes through Settings.publish; the base class owns namespace registration, resolution, validation, change detection, and the `settings/updated` commit event. - -```ts cordis-catalog -/** - * Prepare the provider's user-editable document for a native editor. File - * providers may materialize an absent document before returning its path; - * non-file providers return undefined. - * @returns the absolute local document path, or undefined for non-file storage. - */ -prepareDocument(): Promise - -/** - * Register a namespace schema and receive its owner scope. The registration - * is an effect on the calling plugin's fiber: disposing that fiber removes - * the namespace and its observers. An invalid stored section fails the - * registration itself — the earliest point where the schema can judge it. - * @param ns - unique namespace; duplicate registration fails loud. - * @param schema - schemastery schema resolving this namespace's value. - * @param options - composition `base` layer and effect timing. - * @returns the owner scope for reads, observation, and updates. - */ -register(ns: SettingsNamespace, schema: z, options?: SettingsRegisterOptions): SettingsScope - -/** - * Describe every registered namespace for configuration surfaces, including - * the composition `base` and raw user layers so a form can mark which fields - * the user overrode (presence in `user`) and what a reset returns to. - * @param options - redaction switch; wire surfaces must redact. - * @returns one descriptor per registered namespace, in registration order. - */ -describe(options?: SettingsDescribeOptions): SettingsDescriptor[] - -/** - * Read one registered namespace's resolved value. - * @param ns - the namespace to read. - * @returns the resolved value, or `undefined` while unregistered. - */ -get(ns: SettingsNamespace): unknown - -/** - * Merge a patch into one registered namespace's user layer, validate the - * resolved candidate, persist through the provider, then commit and emit. - * A validation failure rejects before anything is persisted. Writes to one - * namespace are serialized: concurrent updates apply in call order, each - * merging over the previous write's committed section. - * @param ns - the registered namespace to update. - * @param patch - plain-object patch over the user section. - * @param expectedRevision - the descriptor `revision` the caller read; a - * namespace that moved past it rejects with {@link SettingsConflictError}. - */ -async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise - -/** - * Replace one registered namespace's user section wholesale, validate, - * persist, then commit and emit. Keys absent from `section` fall back to the - * composition `base` and schema defaults — this is the removal/reset path a - * merge-only patch cannot express (`replace({})` re-inherits everything). - * @param ns - the registered namespace to replace. - * @param section - the complete next user section. - * @param expectedRevision - the descriptor `revision` the caller read; a - * namespace that moved past it rejects with {@link SettingsConflictError}. - */ -async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise - -/** - * Apply path-addressed edits to one registered namespace's user section, - * validate, persist, then commit and emit. The ops are applied to the - * section as it stands when the write reaches the front of the queue, so a - * caller never has to restate fields it did not touch — and, crucially, - * cannot delete fields it never saw. This is the write path for any caller - * holding a redacted view; `replace` remains the wholesale reset. - * @param ns - the registered namespace to edit. - * @param ops - ordered path edits; later ops observe earlier ones. - * @param expectedRevision - the descriptor `revision` the caller read; a - * namespace that moved past it rejects with {@link SettingsConflictError}. - */ -async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise -``` - -Types: [SettingsDescribeOptions](../subsystems/settings.md) · [SettingsDescriptor](../subsystems/settings.md) · [SettingsNamespace](../subsystems/settings.md) · [SettingsPathOp](../subsystems/settings.md) · [SettingsRegisterOptions](../subsystems/settings.md) · [SettingsScope](../subsystems/settings.md) - -Source: [`packages/settings/settings/src/index.ts:387`](../../packages/settings/settings/src/index.ts) - -## `ctx.skills` — `SkillService` - -Registry of skill providers. It merges provider catalogs with stable first-wins duplicate handling, exposes sorted invocation-neutral summaries, and loads full skill bodies on demand. - -```ts cordis-catalog -/** - * Register a borrowed same-process provider synchronously during plugin apply. Duplicate and - * reserved names throw; remote initialization belongs in `list()`. Fiber disposal unregisters - * the provider and invalidates catalog caches. - * @param create - synchronous factory receiving this registration's lifecycle and invalidation control. - * @returns the exact Cordis effect disposer that unregisters this provider; - * composite effects may yield it directly to preserve teardown ordering. - */ -registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void - -/** - * Register a borrowed readonly runtime skill. Project entries outrank runtime entries, which - * outrank user entries. Same-name runtime entries are first-wins; a duplicate logs a warning and - * receives a no-op disposer so it cannot remove the winner. - * @param skill - the skill definition input; omitted invocation and provider fields receive defaults. - * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches. - */ -register(skill: SkillRegistration): () => void - -/** - * List invocation-neutral skill summaries for a workspace. Consumers apply - * model or user invocation policy at their operational boundary. Lookup - * options and provider candidates are readonly same-process values borrowed - * throughout discovery. - * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. - * @returns all sorted winning summaries. - */ -async list(options: SkillLookupOptions = {}): Promise - -/** - * Observe the current invocation-neutral catalog and whether discovery completed within a stable revision. - * Incomplete observations are never cached, allowing consumers to retain last-good state and - * retry on their next request boundary. - * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. - * @returns sorted summaries plus discovery-completeness state. - */ -async snapshot(options: SkillLookupOptions = {}): Promise - -/** - * Load and validate the winning candidate, passing its opaque discovery locator back to the - * provider. Cancellation is rechecked after selection, including cache hits, and raced against - * loading so an uncooperative provider cannot hang the caller. - * @param name - kebab-case skill name. - * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work. - * @returns the full skill, including body content, or `undefined`. - */ -async get(name: string, options: SkillLookupOptions = {}): Promise -``` - -Types: [SkillCatalogSnapshot](../subsystems/skills.md) · [SkillDefinition](../subsystems/skills.md) · [SkillLookupOptions](../subsystems/skills.md) · [SkillProvider](../subsystems/skills.md) · [SkillProviderControl](../subsystems/skills.md) · [SkillRegistration](../subsystems/skills.md) · [SkillSummary](../subsystems/skills.md) - -Source: [`packages/skill/skill/src/index.ts:304`](../../packages/skill/skill/src/index.ts) - -## `ctx.spillStore` — `SpillStore` (abstract seam) - -Abstract spill storage service. Subclass, implement saveText, and load the subclass as a plugin — it registers as `ctx.spillStore` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). - -Semantics every implementation must honor: - -- saveText persists the FULL `content` verbatim and returns an opaque locator, exact byte length, and model-facing retrieval guidance. -- Storage is scoped by the request's SaveTextSpill.owner session; the backend chooses a private (not world-readable) location and a collision-free name derived from — never equal to — the caller's `suggestedName`. -- `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable); the caller decides how to degrade (the spill policy treats a rejection as best-effort and keeps the inline result). - -```ts cordis-catalog -/** - * Persist `input.content` to a session-scoped spill artifact. - * @param input - the owner, provenance, suggested name, and full text to save. - * @returns the saved artifact's {@link SpillRef}; rejects on a storage failure. - */ -abstract saveText(input: SaveTextSpill): Promise -``` - -Types: [SaveTextSpill](../subsystems/spill.md) · [SpillRef](../subsystems/spill.md) - -Source: [`packages/spill/spill/src/index.ts:45`](../../packages/spill/spill/src/index.ts) - -## `ctx.storage` — `Storage` - -The storage hub service. Backends register under `backend`; data forms mount under their `StorageForms` key and are reached as `ctx.storage.
`. - -```ts cordis-catalog -/** - * Mount a data-form facility on the hub. Mounting is an effect: the - * returned disposer unmounts the form. - * @param form - Form key declared in {@link StorageForms}. - * @param facility - The facility instance to expose. - * @returns the disposer that unmounts the form. - */ -mount(form: K, facility: StorageForms[K]): () => void - -/** - * Resolve a mounted data form. - * @param form - Form key declared in {@link StorageForms}. - * @returns the mounted facility. - */ -form(form: K): StorageForms[K] -``` - -Types: [StorageForms](../subsystems/storage.md) - -Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts) - -## `ctx.storageDomain` — `DomainFacility` - -The mounted domain facility. Opens declared domains over routed backends; one facility instance owns the open-domain table and enforces single-open per domain name. - -```ts cordis-catalog -/** - * Open one declared domain. Steps, each failing the whole call: reject a - * name that is already open (`already-open`); resolve the backend route - * (`backend-not-found` passes through from the hub); require its `kv` facet - * (`facet-unsupported`); open the unit projected from the spec (backend - * `version-mismatch`/`malformed-medium` pass through); load and validate - * every stored record against the spec's zod schemas (`invalid-record` - * with the offending table and key); construct the domain. - * - * Lifecycle: the CALLER owns the returned handle and closes it via - * `Domain.close()` (typically as its own `ctx.effect` disposer) — the - * facility does not tie the domain to any consumer fiber. Domains still - * open when the facility unmounts are closed by the plugin disposer. - * @param spec - The domain declaration, typically from `defineDomain`. - * @returns the opened domain handle, typed by the spec. - */ -async open(spec: S): Promise> - -/** - * Look up an open domain by name, untyped. Diagnostic surface (the package - * invariant cross-checks change events against live domain state); typed - * consumers hold the handle returned by {@link open}. - * @param name - Domain name. - * @returns the open domain runtime, or `undefined` when not open. - */ -get(name: string): DomainImpl | undefined - -/** - * Close every domain still open on this facility. The unmount path for - * consumers that never called `Domain.close()` themselves; closing is - * idempotent, so double-closing an already-closed domain is harmless. - * @returns resolution after every unit is released. - */ -async closeAll(): Promise -``` - -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) - -## `ctx.subagents` — `SubagentService` - -Named provider registry with one-shot runs, durable discovery, and continuable-child operations. - -```ts cordis-catalog -/** - * Establish one durable continuable child and deliver its initial prompt. - * Resolves when the child's inbox accepts that prompt, without waiting for the - * turn to start or for the message to reach the Session log; any earlier - * failure rejects with no ids and rolls back the child entirely. - * @param spec - provider, delegation request, and caller cancellation. - * @returns the durable child id and the accepted prompt's message id. - * @throws when continuation services are unavailable or materialization fails. - */ -async startContinuable(spec: ContinuableStartSpec): Promise - -/** - * Deliver one later message to a continuable child as its next FIFO turn. A - * resident child's Agent inbox accepts it directly (waking a `waiting` - * Activation), while an absent one is cold-resumed from its persisted - * Session. The Agent inbox is the only queue, so every accepted message has - * one observable order. - * @param parent - the exact live direct parent authorizing this delivery. - * @param childId - durable child session id. - * @param content - user-role content to deliver. - * @param options - durable provenance and caller cancellation, which stops the - * operation only before inbox acceptance. - * @returns the accepted message's inbox id. - * @throws when continuation services are unavailable, parent authority is - * rejected, or the message was not admitted. - */ -async followup( parent: Agent, childId: SessionId, content: ContentBlock[], options: SubagentFollowupOptions, ): Promise - -/** - * Interrupt one live continuable child's current turn under a human parent - * address or an exact live ancestor Agent. Fire-and-return: the cancel - * signal is issued before this returns, but the target may keep running - * until it observes the signal. Unclaimed pending inbox work, the Activation, - * and published descendants are preserved; claimed work is not requeued. - * Once the interrupted driver is idle, a waking send resumes the parked FIFO - * queue. An absent target — including a one-shot or unknown id — - * is an accepted no-op, as is a manager-less composition, which cannot own a - * live Activation. - * @param targetSessionId - the durable child session id to interrupt. - * @param authority - the human parent address or exact live ancestor Agent. - * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the - * live target. - */ -interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void - -/** - * Deliver selected content from one live continuable child to its durable - * direct parent. The child is the authority credential; callers cannot name a - * recipient. Reporting does not conclude the child's turn or Activation. - * @param child - exact live reporting child. - * @param content - selected model-facing content. - * @param options - parent scheduling and pre-acceptance cancellation. - * @returns the stable identity of the parent-accepted message. - * @throws when continuation services are unavailable, sender authorization - * fails, or the direct parent is not live. - */ -async reportFrom( child: Agent, content: ContentBlock[], options: SubagentReportOptions, ): Promise - -/** - * Compose one deployment capability into every continuable child's - * unpublished creation context on fresh creation and cold resume. Grants wait - * for the next Activation; removing the contribution revokes every resident - * installation immediately. - * @param contribution - synchronous child-scope installer. - * @returns the exact Cordis effect disposer. - */ -registerContinuableSetup(contribution: ContinuableSetupContribution): () => void - -/** - * Close continuable admission below exact live parent Agents, stop only their - * visible descendant Activations synchronously, then await admitted scoped - * materializations and release those forests child-first. The scoped cutoff - * lasts until each exact parent leaves the registry; unrelated parent trees - * remain live. - * @param parents - exact host-owned parent Agents entering teardown. - * @returns once every retained descendant Activation released its `AgentHandle`. - * @throws an aggregate error after all branches settle when any failed. - */ -async drainContinuableDescendants(parents: readonly Agent[]): Promise - -/** - * Enumerate the parent's direct session-backed subagents without loading or - * resuming an Agent and without any query seam: the listing merges the live - * session store with optional session persistence (live-preferred) and - * serves each child's durable mode/label from the registered `subagent` - * projection unit down a three-rung ladder — the registry's watermark - * snapshot for a live child; for a cold one, a durable projection-cache - * row when the optional cache serves an own-suffix identity (its `seq` - * gate proves the value postdates the fork seed, where a child's own - * descriptor is immutable once appended), else one persistence inspection - * folded through the registry. The - * projection fold is the single classification authority; per-child - * diagnostics relay a fold that served no identity or a failed inspection, - * never a list-time descriptor parse. Absent persistence, enumeration is - * live-only (a cold child cannot be resumed then either, so its absence is - * capability absence, not an error). This service consults no Agent - * registrations, Activations, or providers. - * - * Every persistence read receives `signal`, and the listing rechecks - * cancellation around each of those awaits. Read rejections that settle - * after an abort become a stable `SubagentError` with code `CANCELLED`. - * @param parentSessionId - parent session whose direct children are listed. - * @param signal - caller-owned cancellation forwarded to persistence reads - * and observed around every read await. - * @returns children and per-child diagnostics ordered by `createdAt`, then id. - * @throws {@link SubagentError} when the projection registry or the session - * store is not mounted, or the caller cancels the listing. - */ -listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise - -/** - * Enumerate the root's complete session-backed subagent tree in stable - * pre-order from one live-preferred corpus, without loading or resuming an - * Agent. Ordinary sessions and one-shot children remain traversal nodes so - * continuable descendants below them are discovered; each returned entry - * adds its durable `parentId` and root-relative `depth`. Identity resolution, - * diagnostics, optional persistence, and cancellation follow the same - * projection-backed contract as {@link listChildren}. - * @param rootSessionId - session whose complete descendant tree is listed. - * @param signal - caller-owned cancellation forwarded to persistence reads - * and observed around every read await. - * @returns children and per-candidate diagnostics with tree position, in - * stable pre-order. - * @throws {@link SubagentError} under the same conditions as {@link listChildren}. - */ -listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise - -/** - * Register a provider under its name. Registration is effect-scoped and HMR - * safe; removing a provider blocks new starts but does not revoke runs that - * were already returned to their holders. - * @param provider - the trusted provider implementation. - * @returns the exact Cordis effect disposer. - */ -registerProvider(provider: SubagentProvider): () => void - -/** - * Look up a provider by name. - * @param name - the provider name. - * @returns the provider, or undefined when absent. - */ -getProvider(name: string): SubagentProvider | undefined - -/** - * List registered provider names in insertion order. - * @returns the registered names. - */ -list(): string[] - -/** - * Establish a published child on the named provider. Capability and semantic - * checks run before delegation. Provider ownership lasts until its promise - * fulfills; a rejection therefore has no run for the caller to dispose and - * emits no run lifecycle events. Post-publication turn and infrastructure - * failures settle through the returned run. - * @param name - the provider to use. - * @param request - child label, prompt, parent, signal, and optional capabilities. - * @returns the published holder-owned run. - */ -async start(name: string, request: SubagentStartRequest): Promise -``` - -Types: [Agent](../subsystems/core.md) · [ContentBlock](../subsystems/core.md) · [ContinuableSetupContribution](../subsystems/subagent.md) · [ContinuableStart](../subsystems/subagent.md) · [ContinuableStartSpec](../subsystems/subagent.md) · [MessageId](../subsystems/core.md) · [SessionId](../subsystems/core.md) · [SubagentDescendantListEntry](../subsystems/subagent.md) · [SubagentFollowupOptions](../subsystems/subagent.md) · [SubagentInterruptAuthority](../subsystems/subagent.md) · [SubagentListEntry](../subsystems/subagent.md) · [SubagentProvider](../subsystems/subagent.md) · [SubagentReportOptions](../subsystems/subagent.md) · [SubagentRun](../subsystems/subagent.md) · [SubagentStartRequest](../subsystems/subagent.md) - -Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) - -## `ctx.subprocess` — `SubprocessService` (abstract seam) - -Abstract subprocess service. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.subprocess` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). - -Implementations must honor these semantics: - -- Executable paths belong to one execution world shared with the mounted filesystem provider. -- spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures. -- Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists. Piped streams are handed to the caller raw and never buffered here. -- SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence. -- Disposal of the service terminates all still-running managed processes and awaits their exit. -- spawnTerminal owns terminal allocation, text transport, foreground groups, signalling, and whole-session quiescence behind one awaited termination method; readiness and persistent-shell policy stay in the PTY consumer. Its output stream ends after queued terminal output when the top-level process exits. - -```ts cordis-catalog -/** - * Resolve one configured executable in this provider's execution world. - * Absolute paths are verified; bare names use the provider's scrubbed PATH - * plus explicit environment overrides. Relative paths containing separators - * are rejected: no current consumer defines which directory they would - * resolve against, so providers fail loud instead of guessing. - * @param command - absolute executable path or bare PATH name. - * @param env - explicit environment entries used for lookup. - * @param signal - aborts remote or local lookup. - * @returns a canonical executable path. - */ -abstract resolveExecutable( command: string, env?: Readonly>, signal?: AbortSignal, ): Promise - -/** - * Start one managed child process from a fully-specified spec; this seam - * applies no defaults. - * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment. - * @returns the live process handle (streams/readers, signalling, outcome promise). - */ -abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle - -/** - * Allocate a real terminal and start one owned process session. This is the - * only non-pipe process primitive: implementations own terminal byte I/O, - * foreground groups, signals, and complete session-tree cleanup. - * @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation. - * @returns the live terminal handle after allocation succeeds. - */ -abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise -``` - -Types: [SubprocessHandle](../subsystems/subprocess.md) · [SubprocessSpawnSpec](../subsystems/subprocess.md) · [SubprocessTerminalHandle](../subsystems/subprocess.md) · [SubprocessTerminalSpawnSpec](../subsystems/subprocess.md) - -Source: [`packages/subprocess/subprocess/src/index.ts:102`](../../packages/subprocess/subprocess/src/index.ts) - -## `ctx.systemPrompt` — `SystemPrompt` - -Registry service for the prompt inputs assembled before each model step. - -```ts cordis-catalog -/** - * Register an ordered prompt section in the calling context's scope. A scoped - * section shadows a global section with the same name; duplicates within one - * layer and non-finite orders throw. Registration and disposal emit - * `system-prompt/change`. - * @param section - the section to register. - * @returns the exact Cordis effect disposer. - */ -section(section: PromptSection): () => void - -/** - * Register ordered dynamic context in the calling context's scope. Scoped - * entries shadow global entries with the same name. - * @param context - the context contribution to register. - * @returns the exact Cordis effect disposer. - */ -context(context: PromptContext): () => void - -/** - * Register a tool-schema provider in the calling context's scope. Global and - * matching scoped providers both contribute; returning the reserved - * {@link TOOL_ORDER_REST} name makes assembly fail. - * @param provider - evaluated for each assembly with its context. - * @returns the exact Cordis effect disposer. - */ -tools(provider: (context: AssembleContext) => ToolProviderResult): () => void - -/** - * Register a prompt variable in the calling context's scope. Scoped values - * shadow globals; invalid or duplicate names throw. A provider may return - * `undefined`, but rendering a section that references that value then fails. - * @param name - the `[a-z][a-z0-9_]*` reference name. - * @param provider - evaluated for each assembly. - * @returns the exact Cordis effect disposer. - */ -variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void - -/** - * Assemble global and scoped providers, detach tool parameters, apply - * canonical ordering, then run the assembly waterfall. Scoped sections and - * variables shadow globals; the returned waterfall value is authoritative. - * @param context - the optional scope and plugin-defined assembly fields. - * @returns the authoritative post-waterfall assembly. - */ -async assemble(context: AssembleContext = {}): Promise -``` - -Types: [AssembleContext](../subsystems/system-prompt.md) · [PromptContext](../subsystems/system-prompt.md) · [PromptSection](../subsystems/system-prompt.md) · [ToolProviderResult](../subsystems/system-prompt.md) - -Source: [`packages/core/system-prompt/src/index.ts:314`](../../packages/core/system-prompt/src/index.ts) - -## `ctx.tasks` — `TaskService` (abstract seam) - -Abstract background task registry. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.tasks` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). - -Implementations must honor these semantics: - -- Registrations outlive producer and control-surface fibers. Owner and service disposal cancel live work and await compliant producers; a throwing teardown cancel force-fails only the record. -- Owned-task access is fenced by the owner's session id. Ids are predictable, so authorization — not secrecy — is the boundary. -- Settlement is first-wins: one terminal record, one round of contained listener notification, and released waiters, even against a late producer outcome. -- start refuses work while no control surface is attached, so a producer cannot start work that callers cannot collect or stop. - -```ts cordis-catalog -/** - * Preflight access, validation, and owner cleanup before starting and - * atomically registering work. A throwing starter leaves nothing registered; - * after it returns, registration cannot fail. Settlement records the outcome, - * notifies listeners, and releases waiters. - * @param spec - task identity, owner, and synchronous starter. - * @returns the registry-issued `-N` id. - */ -abstract start(spec: TaskStart): TaskId - -/** - * List caller-owned and unowned tasks in registration order without exposing - * another session's labels. - * @param caller - reading agent; a non-agent caller sees only unowned tasks. - * @returns fresh snapshots. - */ -abstract list(caller?: Agent): TaskSnapshot[] - -/** - * Return a non-consuming snapshot without changing its read cursor or notice - * state. Throws for an unknown or foreign task. - * @param id - task to look up. - * @param caller - reading agent checked against the owner. - * @returns a fresh snapshot. - */ -abstract get(id: TaskId, caller?: Agent): TaskSnapshot - -/** - * Read the next stream delta, or the idempotent final output after settlement. - * A terminal read marks the task reported. Throws for an unknown or foreign - * task. - * @param id - task to read. - * @param caller - reading agent checked against the owner. - * @returns output text and the post-read snapshot. - */ -abstract read(id: TaskId, caller?: Agent): TaskRead - -/** - * Request cancellation, then mark the task stopping and reported. A producer - * throw propagates without changing task state. Throws for an unknown or - * foreign task. - * @param id - task to cancel. - * @param caller - killing agent checked against the owner. - * @param reason - logged reason forwarded to the producer. - * @returns `requested` for live work, otherwise `already-finished`. - */ -abstract kill(id: TaskId, caller?: Agent, reason?: string): 'requested' | 'already-finished' - -/** - * Wait for settlement or timeout without cancelling the task. Caller abort - * rejects only while the task is live; after settlement the terminal - * snapshot wins so a notice suppressed for this waiter is still delivered. - * Throws for invalid, unknown, or foreign input. - * @param id - task to wait for. - * @param timeoutMs - positive finite wait bound in milliseconds. - * @param caller - waiting agent checked against the owner. - * @param signal - optional cancellation of the wait itself. - * @returns snapshot at settlement or timeout. - */ -abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise - -/** - * Register an effect-scoped completion listener. Each listener is contained; - * returned promises are observed but not awaited. No listener runs after - * service disposal. - * @param listener - receives each terminal snapshot and its exact owner. - * @returns disposer that unregisters the listener. - */ -abstract onTaskDone(listener: TaskDoneListener): () => void - -/** - * Attach an effect-scoped surface that can read and stop tasks. {@link start} - * refuses work while none is attached. - * @param name - diagnostic label; duplicate names remain independent. - * @returns disposer that detaches this surface. - */ -abstract attachSurface(name: string): () => void -``` - -Types: [Agent](../subsystems/core.md) · [TaskDoneListener](../subsystems/tasks.md) · [TaskId](../subsystems/tasks.md) · [TaskRead](../subsystems/tasks.md) · [TaskSnapshot](../subsystems/tasks.md) · [TaskStart](../subsystems/tasks.md) - -Source: [`packages/tasks/tasks/src/index.ts:50`](../../packages/tasks/tasks/src/index.ts) - -## `ctx.telemetry` — `Telemetry` (abstract seam) - -The backend contract in its loadable form: one implementation per context — the cordis `Service` registration under the `telemetry` key throws on a duplicate, cordis' standard behavior. A backend composes a TelemetryCoordinator in its constructor to install the capture side. - -```ts cordis-catalog -/** - * See {@link TelemetryBackend.emit} — the seam declaration is the contract's one home. - * @param record - the logical record to report; owned by the backend after the call. - */ -abstract emit(record: TelemetryRecord): void - -/** See {@link TelemetryBackend.flush}. */ -flush?(): void - -/** - * See {@link TelemetryBackend.shutdown}. - * @returns resolves when the backend's pipeline has quiesced. - */ -abstract shutdown(): Promise -``` - -Types: [TelemetryRecord](../subsystems/telemetry.md) - -Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts) - -## `ctx.tokenMeter` — `TokenMeterService` - -Replay owner for one service-wide estimator and isolated per-session folds. - -```ts cordis-catalog -/** - * Measure current request pressure and surface through the durable tail. - * - * Provider usage is reused only when the latest successful call's canonical - * request envelope matches `requestHeader` and its total is no lower than - * that call's full heuristic anchor; otherwise the complete envelope and - * surface are heuristically repriced. - * - * `requestHeader` affects request pressure only; surface fields always - * describe the current session surface. Every call clones those positional - * nodes, so measurement is O(surface). - * - * @param session - session to replay through its current durable tail. - * @param requestHeader - optional effective request envelope replacing the latest logged header. - * @returns a detached deeply immutable pressure and surface measurement. - */ -measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement - -/** - * Heuristically price one model-visible message (instance face of the pure - * `estimateMessage` export from `estimate.ts`). - * @param message - message to price without mutation. - * @returns content and role-framing tokens under the fixed service heuristic. - */ -estimateMessage(message: Message): number -``` - -Types: [EpochHeader](../subsystems/session.md) · [Message](../subsystems/core.md) · [Session](../subsystems/session.md) · [TokenMeasurement](../subsystems/token-meter.md) - -Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) - -## `ctx.toolResultPrune` — `ToolResultPruneService` - -Deterministic head/middle/tail pruning for current tool-result surface nodes. - -```ts cordis-catalog -/** - * Measure text content in Unicode code points; non-text blocks cost zero. - * @param blocks - tool-result content to measure. - * @returns total Unicode code points across text blocks. - */ -measureContent(blocks: readonly ContentBlock[]): number - -/** - * Replace an over-budget text middle while retaining rich-block order. - * Text slicing is by Unicode code point, not UTF-16 code unit, so a retained - * boundary cannot split a surrogate pair. Grapheme clusters may still split. - * @param blocks - original tool-result content. - * @returns pruned content, or `null` when the text is within budget. - */ -pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null - -/** - * Prune every over-budget tool result from one stable current-surface snapshot. - * Each replacement preserves the complete event data except for `content`, - * points at the shadowed node for durable provenance and replay, and is - * immediately preceded by a `compact/prune` shadow-price event pricing the - * shadowed node through the injected token meter, so pure consumers can - * subtract it without per-node state. - * @param session - session whose current surface is rewritten. - * @returns landed replacements and aggregate Unicode-code-point savings. - * @throws when the session rejects a replacement; replacements committed - * earlier in the pass remain durable. - */ -pruneSession(session: Session): PruneResult -``` - -Types: [ContentBlock](../subsystems/core.md) · [PruneResult](../subsystems/compaction.md) · [Session](../subsystems/session.md) - -Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) - -## `ctx.tools` — `ToolRegistry` - -Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch. - -```ts cordis-catalog -/** - * Register globally or in the calling agent scope. Scoped tools shadow - * globals; duplicates within one layer and the reserved `run_code` name fail. - * @param definition - tool schema, execution, and optional finalization/presentation callbacks. - * @returns the exact disposer that unregisters the tool. - */ -register(definition: ToolDefinition): () => void - -/** - * Restrict global tools for the calling agent scope. Empty filters, unknown - * names, scope-local names, and reserved transport names fail. Restrictions - * intersect; scoped registrations remain visible. - * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove). - * @returns the exact disposer that lifts this restriction. - */ -restrict(filter: ToolRestriction): () => void - -/** - * Register a monotonic guard after the extensible `tools/pre-execute` - * waterfall. A plain-context guard applies globally; one registered through - * `agent.ctx` applies only to that agent. Any matching guard may deny by - * returning a reason, while no guard can force-allow a call another guard - * denied. The exact effect disposer is returned for ordered ownership and - * HMR cleanup. - * @param guard - synchronous check; a returned string denies the execution. - * @returns the exact disposer that unregisters the guard. - */ -guard(guard: ToolGuard): () => void - -/** - * Look up a tool as one scope sees it (scoped - * shadows global; a restricted-away global reads as absent). Presenters pass - * the calling agent so the rendered card matches the definition that - * actually executed. - * @param name - the tool name as registered. - * @param scope - the viewing scope (the agent); omitted = the global view. - * @returns the definition the scope resolves, or undefined when none is visible. - */ -get(name: string, scope?: ScopeKey): ToolDefinition | undefined - -/** - * Project visible definitions onto the allowlisted model-facing schema fields, - * excluding execution and presentation callbacks. - * @param scope - the viewing scope (the agent); omitted = the global view. - * @returns one deep-cloned schema per visible tool. - */ -schemas(scope?: ScopeKey): ToolSchema[] - -/** - * Classify a pending call through the caller's visible tool definition. Only - * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or - * throwing classifiers are exclusive. - * @param exec - call name, parsed arguments, and optional agent scope. - * @returns the fail-closed scheduling mode. - */ -executionMode(exec: ToolExecutionInput): ToolExecutionMode - -/** - * Execute through pre-policy, guards, around-dispatch, post-policy, - * definition-owned content finalization, and final notification. Tool and - * listener failures resolve as materialized error results; an invisible tool - * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen - * snapshot final observers receive. Cancellation - * arriving after entry and before final result materialization skips a - * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a - * successful started outcome with `ABORTED`; already-started work is still - * drained and may retain a tool-owned structured error. - * @param exec - the typed same-process call input. The registry assigns its - * correlation token before policy begins. - * @returns the materialized final result. - */ -async execute(exec: ToolExecutionInput): Promise -``` - -Types: [ScopeKey](../subsystems/scope.md) · [ToolDefinition](../subsystems/tools.md) · [ToolExecutionInput](../subsystems/tools.md) · [ToolExecutionMode](../subsystems/tools.md) · [ToolExecutionResult](../subsystems/tools.md) · [ToolGuard](../subsystems/tools.md) · [ToolRestriction](../subsystems/tools.md) · [ToolSchema](../subsystems/tools.md) - -Source: [`packages/core/tools/src/index.ts:739`](../../packages/core/tools/src/index.ts) - -## `ctx.typert` — `TypertRegistry` - -Registry of generated schemas, package reflection, invocations, and Remote dependency providers. - -```ts cordis-catalog -/** - * Register one generated contribution atomically for the calling fiber. - * Duplicate package-face identities, schemas, invocation ids, or endpoints - * reject the whole batch. - * @param contribution - generated schemas, reflection, and Host invocations. - * @returns the exact effect disposer that removes this contribution. - */ -register(contribution: TypertContribution): TypeRTDisposer - -/** - * Look up one schema by `#`. - * @param key - global schema key. - * @returns the live schema record, or `undefined` when absent. - */ -get(key: string): TypertSchemaRecord | undefined - -/** - * Resolve one required schema. - * @param key - global schema key. - * @returns the live schema record. - * @throws when the key is malformed, the package face is absent, or the schema is not contributed. - */ -resolve(key: string): TypertSchemaRecord - -/** - * Enumerate live schemas in registration order. - * @param filter - optional package and face restriction. - * @returns matching schema records. - */ -list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] - -/** - * Look up generated reflection for one package face. - * @param packageName - exact npm package name. - * @param face - face to query; defaults to the host runtime. - * @returns the live package record, or `undefined` when absent. - */ -getPackage(packageName: string, face: TypertFace = 'host'): TypertPackageRecord | undefined - -/** - * Enumerate generated package reflection in registration order. - * @param filter - optional package and face restriction. - * @returns matching package records. - */ -listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] - -/** - * Project a live Zod schema to JSON Schema without caching the result. - * @param key - global schema key. - * @param params - Zod projection parameters. - * @returns a fresh JSON Schema document. - */ -toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema -``` - -Source: [`packages/typert/registry/src/service.ts:446`](../../packages/typert/registry/src/service.ts) - -## `ctx.typertGateway` — `TypertGatewayService` - -Resolve strict generated definitions or conservative SRC markers against current Cordis Services and TypeRT providers. - -```ts cordis-catalog -/** - * Invoke one live Remote method through strict generated reflection or SRC markers. - * @param request - decoded endpoint and exact named wire arguments. - * @returns the validated business result. - * @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity. - */ -async invoke(request: InvokeRemoteRequest): Promise -``` - -Source: [`packages/api/gateway/src/index.ts:78`](../../packages/api/gateway/src/index.ts) - -## `ctx.userInteraction` — `UserInteractionService` - -`ctx.userInteraction`: one active UI provider plus an `ask()` surface. - -```ts cordis-catalog -/** - * Register the UI provider. Only one provider may be active in a context. - * - * @param provider UI-side implementation that collects answers. - * @returns Disposer that unregisters this provider. - */ -registerProvider(provider: UserInteractionProvider): () => void - -/** - * Ask the active UI provider and wait for the user's answer. - * - * When a caller supplies an agent, human interaction is valid only for the - * exact live runtime root. Runtime ownership, not durable session lineage, - * decides this boundary: an owned child has no human answerer and would - * block forever, while a lineage-bearing session resumed as a new runtime - * root may ask normally. - * - * @param request Questions, owner agent, and abort signal. - * @returns The answer chosen or typed by the human. - * @throws {UserInteractionError} code `CALLER_NOT_LIVE` when a supplied - * agent is not the registry's exact live instance, or `DELEGATED_CALLER` - * when that live agent is owned by another agent. - */ -async ask(request: AskUserQuestionRequest): Promise -``` - -Types: [AskUserQuestionAnswer](../subsystems/user-interaction.md) · [AskUserQuestionRequest](../subsystems/user-interaction.md) · [UserInteractionProvider](../subsystems/user-interaction.md) - -Source: [`packages/interaction/user-interaction/src/index.ts:51`](../../packages/interaction/user-interaction/src/index.ts) - -## `ctx.web` — `WebService` - -The web access service. Registered as `ctx.web` (one instance per context). - -Selection semantics (resolved at execution time, never order-dependent): - -- A configured id that is registered and `available()` → that provider. -- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. -- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. -- No id configured, exactly one registered usable provider → that provider. -- No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. -- No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. - -```ts cordis-catalog -/** - * Register a search provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` - * if its id is already registered for search. Returns a disposer; disposed - * with the calling fiber. - * @param provider - the provider; its `id` is the registry key. - * @returns the disposer that unregisters the provider. - */ -registerSearchProvider(provider: WebSearchProvider): () => void - -/** - * Register a fetch provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` - * if its id is already registered for fetch. Returns a disposer; disposed - * with the calling fiber. - * @param provider - the provider; its `id` is the registry key. - * @returns the disposer that unregisters the provider. - */ -registerFetchProvider(provider: WebFetchProvider): () => void - -/** - * Run one search through the selected provider. Resolves the provider at call - * time with the selection rules above; throws {@link WebError} when the - * capability cannot run. The seam enforces `request.maxResults` on the result: - * if the provider over-returns, `sources[]` is truncated and `truncated` set. - * @param request - the query plus result-shaping options. - * @param signal - optional cancellation signal forwarded to the provider. - * @returns the provider's results, capped to `request.maxResults`. - */ -async search(request: WebSearchRequest, signal?: AbortSignal): Promise - -/** - * Retrieve one URL through the selected provider. Resolves the provider at - * call time with the selection rules above; throws {@link WebError} when the - * capability cannot run. A non-2xx response is a result, not a throw. - * @param request - the URL plus retrieval options. - * @param signal - optional cancellation signal forwarded to the provider. - * @returns the retrieval outcome; non-2xx responses resolve descriptively. - */ -async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise -``` - -Types: [WebFetchProvider](../subsystems/web.md) · [WebFetchRequest](../subsystems/web.md) · [WebFetchResult](../subsystems/web.md) · [WebSearchProvider](../subsystems/web.md) · [WebSearchRequest](../subsystems/web.md) · [WebSearchResult](../subsystems/web.md) - -Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts) - -## `ctx.workflows` — `WorkflowService` (abstract seam) - -Workflow execution seam. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles. - -```ts cordis-catalog -/** - * Parse and execute a workflow script. - * @param request - the script, its `args`, the parent agent, and an - * optional cancel signal. - * @returns the live run; its `result` resolves when the script settles. - */ -abstract start(request: WorkflowStartRequest): WorkflowRun -``` - -Types: [WorkflowRun](../subsystems/workflow.md) · [WorkflowStartRequest](../subsystems/workflow.md) - -Source: [`packages/workflow/workflow/src/index.ts:159`](../../packages/workflow/workflow/src/index.ts) - -## `ctx.workspace` — `WorkspaceRegistry` - -Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker. - -```ts cordis-catalog -/** - * Create or reuse a workspace for an existing directory. The path is - * canonicalized through `fs.realpath`; a nonexistent path rejects with the - * original error and a non-directory rejects. Repeated calls for the same - * canonical path return the existing entity without changing its title. - * A newly created workspace is prepended to the durable registry order. - * Different canonical paths may share a display title. - * @param path - Existing directory to own, in any path spelling. - * @param title - Display title used only when a new record is created. - * @returns the existing or newly durable workspace. - */ -async create(path: string, title?: string): Promise - -/** - * Look up a workspace by id. - * @param id - Workspace id. - * @returns the workspace, or `undefined` when unknown. - */ -get(id: WorkspaceId): Workspace | undefined - -/** - * Synchronous workspace projection in durable registry order. Every - * entity's `sessionIds` getter is already filtered by the startup/live - * canonical-cwd header index; this method performs no persistence reads. - * @returns a fresh ordered array of workspace entities. - */ -list(): Workspace[] - -/** - * Delete one workspace registration while retaining its directory and every - * session log. The durable order is updated before the table deletion; a - * failed table write restores the prior order and keeps the entity - * published. Unknown ids are an idempotent no-op for domain callers. - * @param id - Workspace registration to remove. - * @returns `true` when a record was deleted, `false` when it was unknown. - */ -delete(id: WorkspaceId): Promise - -/** - * Archive one session durably. The session must exist (live or in session - * persistence); its workspace accounting — or lack of one — is irrelevant. - * An already archived id resolves without writing. - * @param sessionId - The session to archive. - * @returns resolution after durability. - */ -archiveSession(sessionId: SessionId): Promise - -/** - * Resolve by canonical directory path without creating or mutating a - * workspace. A missing path rejects during `realpath`; an existing unowned - * directory returns `undefined`. - * @param path - Existing directory path in any spelling. - * @returns the workspace owning the canonical path, when one exists. - */ -async resolveByPath(path: string): Promise -``` - -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) - -## 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. - -- `ctx.on / ctx.once` — Register an event listener (disposable). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) -- `ctx.emit / ctx.parallel / ctx.serial / ctx.bail / ctx.waterfall` — Dispatch an event (sync / awaited / first-bail / veto-chain). ([`vendor/cordis/src/events.ts:34`](../../vendor/cordis/src/events.ts)) -- `ctx.plugin / ctx.inject` — Load a plugin / declare required services. ([`vendor/cordis/src/registry.ts:164`](../../vendor/cordis/src/registry.ts)) -- `ctx.effect` — Register a disposable side effect tied to the fiber. ([`vendor/cordis/src/fiber.ts:9`](../../vendor/cordis/src/fiber.ts)) -- `ctx.get / ctx.set / ctx.provide / ctx.accessor / ctx.mixin` — Low-level service-store access and binding. ([`vendor/cordis/src/reflect.ts:7`](../../vendor/cordis/src/reflect.ts)) -- `ctx.extend / ctx.isolate / ctx.intercept` — Derive a child context (scoped services / isolation / interception). ([`vendor/cordis/src/context.ts:42`](../../vendor/cordis/src/context.ts)) -- `ctx.root / ctx.scope / ctx.fiber / ctx.registry / ctx.reflect / ctx.events / ctx.logger` — Ambient handles onto the running context graph. ([`vendor/cordis/src/context.ts:16`](../../vendor/cordis/src/context.ts)) -- `ctx.timer (+ interval / timeout / throttle / debounce / setTimeout / setInterval)` — Disposable timer helpers. The `timer` key is provided at runtime; the six helpers are mixed onto ctx directly (declared via Pick). ([`vendor/timer/src/index.ts:4`](../../vendor/timer/src/index.ts)) -- `ctx.loader` — The config Loader that booted the app (present under the loader). ([`vendor/loader/src/index.ts:30`](../../vendor/loader/src/index.ts)) -- `ctx.hmr` — The hot-module-reload watcher (present under the hmr plugin). ([`vendor/hmr/src/index.ts:15`](../../vendor/hmr/src/index.ts)) diff --git a/docs/cordis-primer.i18n.yaml b/docs/cordis-primer.i18n.yaml index 856ea2302b..ec67982eb5 100644 --- a/docs/cordis-primer.i18n.yaml +++ b/docs/cordis-primer.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/cordis-primer.md -cordis-primer.md: ee65e6e702ecaeb506ce7334032c38e09c936cda -cordis-primer.zh.md: 051dd7c956a4db107a4ef7d1414435f9c2b2603d +cordis-primer.md: 4bcb2c9979994ca70f92031cbdc5dd22df9c1977 +cordis-primer.zh.md: d84beac54463ec193f009f506a4c9b1bda02c243 diff --git a/docs/cordis-primer.md b/docs/cordis-primer.md index ee65e6e702..4bcb2c9979 100644 --- a/docs/cordis-primer.md +++ b/docs/cordis-primer.md @@ -2,7 +2,7 @@ English | [中文](cordis-primer.zh.md) -Cordis is the vendored plugin framework underneath the DeepSeek Harness SDK. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated [events](cordis-catalog/events.md) and [services](cordis-catalog/services.md) catalogs; the [Cordis tutorial](cordis-tutorial/index.md) walks the same ideas hands-on. The vendored source and sync procedure live in [vendor/README.md](../vendor/README.md). +Cordis is the vendored plugin framework underneath the DeepSeek Harness SDK. This primer teaches the Cordis ideas a harness plugin author needs before reading the generated service/event reference on the [subsystem pages](subsystems/core.md); the [Cordis tutorial](cordis-tutorial/index.md) walks the same ideas hands-on. The vendored source and sync procedure live in [vendor/README.md](../vendor/README.md). ## Cordis In Five Ideas diff --git a/docs/cordis-primer.zh.md b/docs/cordis-primer.zh.md index 051dd7c956..d84beac544 100644 --- a/docs/cordis-primer.zh.md +++ b/docs/cordis-primer.zh.md @@ -2,7 +2,7 @@ [English](cordis-primer.md) | 中文 -Cordis 是 DeepSeek Harness SDK 底层以 vendor 方式引入的插件框架。本文介绍 harness 插件作者在阅读生成的[事件](cordis-catalog/events.md)与[服务](cordis-catalog/services.md)目录之前需要了解的 Cordis 核心概念;[Cordis 教程](cordis-tutorial/index.md)则通过实践逐一讲解这些概念。vendor 源码与同步流程见 [vendor/README.md](../vendor/README.md)。 +Cordis 是 DeepSeek Harness SDK 底层以 vendor 方式引入的插件框架。本文介绍 harness 插件作者在阅读[子系统页面](subsystems/core.md)上生成的服务/事件参考之前需要了解的 Cordis 核心概念;[Cordis 教程](cordis-tutorial/index.md)则通过实践逐一讲解这些概念。vendor 源码与同步流程见 [vendor/README.md](../vendor/README.md)。 ## 五个核心概念 diff --git a/docs/cordis-tutorial/03-services.i18n.yaml b/docs/cordis-tutorial/03-services.i18n.yaml index 2849ed8858..bdb7e19387 100644 --- a/docs/cordis-tutorial/03-services.i18n.yaml +++ b/docs/cordis-tutorial/03-services.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/cordis-tutorial/03-services.md -03-services.md: 562b49ede0aa4cc1d58c4d6af7c7d5d1ebb2e4b1 -03-services.zh.md: 964f7e3654614d136b8765bb727f85a5a05587a8 +03-services.md: 82b08b7b8a2ec8a6b340dd1fdc7fa3de98cedff9 +03-services.zh.md: ba4152454eb79a21b183b867c0ba2ef32cd43923 diff --git a/docs/cordis-tutorial/03-services.md b/docs/cordis-tutorial/03-services.md index 562b49ede0..82b08b7b8a 100644 --- a/docs/cordis-tutorial/03-services.md +++ b/docs/cordis-tutorial/03-services.md @@ -91,7 +91,7 @@ export function apply(ctx: Context) { ## Naming -Service names live in one flat namespace per application. Prefix or namespace your own services distinctively (the harness claims plain names like `tools` and `llm`); the generated [services catalog](../cordis-catalog/services.md) lists every name the harness registers. +Service names live in one flat namespace per application. Prefix or namespace your own services distinctively (the harness claims plain names like `tools` and `llm`); the generated `cordis-surface` regions on the [subsystem pages](../subsystems/core.md) list every name the harness registers. Next: [Events](04-events.md) — communication without a shared service. diff --git a/docs/cordis-tutorial/03-services.zh.md b/docs/cordis-tutorial/03-services.zh.md index 964f7e3654..ba4152454e 100644 --- a/docs/cordis-tutorial/03-services.zh.md +++ b/docs/cordis-tutorial/03-services.zh.md @@ -91,7 +91,7 @@ export function apply(ctx: Context) { ## 命名 -每个应用中的服务名称共用一个扁平命名空间。请为自有服务添加有辨识度的前缀或命名空间(harness 已占用 `tools` 和 `llm` 等普通名称);生成的[服务目录](../cordis-catalog/services.md)列出 harness 注册的每个名称。 +每个应用中的服务名称共用一个扁平命名空间。请为自有服务添加有辨识度的前缀或命名空间(harness 已占用 `tools` 和 `llm` 等普通名称);[子系统页面](../subsystems/core.md)上生成的 `cordis-surface` 区块列出 harness 注册的每个名称。 下一章:[事件](04-events.md):无需共享服务即可通信。 diff --git a/docs/cordis-tutorial/04-events.i18n.yaml b/docs/cordis-tutorial/04-events.i18n.yaml index e7dc182114..a78b8f690c 100644 --- a/docs/cordis-tutorial/04-events.i18n.yaml +++ b/docs/cordis-tutorial/04-events.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/cordis-tutorial/04-events.md -04-events.md: 28ccb85d657afaabb5c6b4b1e9b10d6cf8710918 -04-events.zh.md: f78c971dcd9674d2a256c41000b627aecb2a572a +04-events.md: b0563c71fada3efc760ff7183bc7a82e7e86934e +04-events.zh.md: 0999cbf116eca452e54418132fbaefe3622b2c26 diff --git a/docs/cordis-tutorial/04-events.md b/docs/cordis-tutorial/04-events.md index 28ccb85d65..b0563c71fa 100644 --- a/docs/cordis-tutorial/04-events.md +++ b/docs/cordis-tutorial/04-events.md @@ -89,7 +89,7 @@ Because `ctx.on()` is an effect, the listener disappears with the plugin — no | bail | `ctx.bail(name, ...args)` | Synchronous version of serial. | | waterfall | `ctx.waterfall(name, ...args, next)` | Around-middleware; see below. | -Every harness event documents its mode in the generated [events catalog](../cordis-catalog/events.md). +Every harness event documents its mode in the generated reference on its owning [subsystem page](../subsystems/core.md). ## Waterfall: transform or short-circuit @@ -137,7 +137,7 @@ Walk through the second line: listener 1 runs first, calls `next()`, which invok The discipline that follows: **a waterfall listener that only observes or annotates must call `next()`**; returning without it is a deliberate short-circuit. Forgetting `next()` in a logging listener silently swallows the default behavior for everyone downstream. This is important enough that it is a standing rule of this repository ([waterfall semantics](../cordis-primer.md#cordis-waterfall-semantics)). -The harness uses waterfalls for decisions that cooperating plugins may wrap or answer: [`agent/request`](../cordis-catalog/events.md#agentrequest--waterfall) lets a plugin replace the model-call config, and [`approval/request`](../cordis-catalog/events.md#approvalrequest--waterfall) lets a policy answer instead of the user. +The harness uses waterfalls for decisions that cooperating plugins may wrap or answer: [`agent/request`](../subsystems/core.md#agentrequest--waterfall) lets a plugin replace the model-call config, and [`approval/request`](../subsystems/approval.md#approvalrequest--waterfall) lets a policy answer instead of the user. Next: [Configuration](05-config.md) — plugin options from `cordis.yml`. diff --git a/docs/cordis-tutorial/04-events.zh.md b/docs/cordis-tutorial/04-events.zh.md index f78c971dcd..0999cbf116 100644 --- a/docs/cordis-tutorial/04-events.zh.md +++ b/docs/cordis-tutorial/04-events.zh.md @@ -89,7 +89,7 @@ export function apply(ctx: Context) { | bail | `ctx.bail(name, ...args)` | serial 的同步版本。 | | waterfall(瀑布式事件) | `ctx.waterfall(name, ...args, next)` | 环绕中间件,见下文。 | -每个 harness 事件都会在生成的[事件目录](../cordis-catalog/events.md)中记录其模式。 +每个 harness 事件都会在其所属[子系统页面](../subsystems/core.md)的生成参考中记录其模式。 ## waterfall:转换或短路 @@ -137,7 +137,7 @@ HELLO 由此得到一项纪律:**只负责观察或标注的 waterfall 监听器必须调用 `next()`**;不调用就直接返回代表有意短路。如果日志监听器忘记调用 `next()`,会悄无声息地吞掉所有下游的默认行为。这一点极其重要,已成为本仓库的常设规则([waterfall 语义](../cordis-primer.md#cordis-waterfall-semantics))。 -harness 使用 waterfall 处理协作插件可以包装或回答的决策:[`agent/request`](../cordis-catalog/events.md#agentrequest--waterfall) 允许插件替换模型调用配置,[`approval/request`](../cordis-catalog/events.md#approvalrequest--waterfall) 允许策略代替用户作答。 +harness 使用 waterfall 处理协作插件可以包装或回答的决策:[`agent/request`](../subsystems/core.md#agentrequest--waterfall) 允许插件替换模型调用配置,[`approval/request`](../subsystems/approval.md#approvalrequest--waterfall) 允许策略代替用户作答。 下一章:[配置](05-config.md):来自 `cordis.yml` 的插件选项。 diff --git a/docs/cordis-tutorial/07-into-the-harness.i18n.yaml b/docs/cordis-tutorial/07-into-the-harness.i18n.yaml index f3dde47f3a..fd29baab75 100644 --- a/docs/cordis-tutorial/07-into-the-harness.i18n.yaml +++ b/docs/cordis-tutorial/07-into-the-harness.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/cordis-tutorial/07-into-the-harness.md -07-into-the-harness.md: e02f8f8d55b3fbe9087d46f8f50baeecb592c1c6 -07-into-the-harness.zh.md: 5f770267e8f3db04cd9cb0e92b6a6278cf05d5e4 +07-into-the-harness.md: 38483b5c4993a44562970782dca5f676e4cb84f6 +07-into-the-harness.zh.md: 59ce716bdace894682bc1c8e6e00cf174008c26c diff --git a/docs/cordis-tutorial/07-into-the-harness.md b/docs/cordis-tutorial/07-into-the-harness.md index e02f8f8d55..38483b5c49 100644 --- a/docs/cordis-tutorial/07-into-the-harness.md +++ b/docs/cordis-tutorial/07-into-the-harness.md @@ -101,7 +101,7 @@ Where to go next: - [Build a tool](../user/develop/basic/tool.md) — more of `defineTool`, including presentation and richer schemas. - [Three-layer capability design](../user/develop/practice/index.md) — how the harness structures replaceable capabilities. -- The generated [services](../cordis-catalog/services.md) and [events](../cordis-catalog/events.md) catalogs — everything you can inject and listen to. +- The generated `cordis-surface` regions on the [subsystem pages](../subsystems/core.md) — everything you can inject and listen to, each on its owning page. - [Architecture](../architecture.md) — the system map these plugins live in. [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk) diff --git a/docs/cordis-tutorial/07-into-the-harness.zh.md b/docs/cordis-tutorial/07-into-the-harness.zh.md index 5f770267e8..59ce716bda 100644 --- a/docs/cordis-tutorial/07-into-the-harness.zh.md +++ b/docs/cordis-tutorial/07-into-the-harness.zh.md @@ -101,7 +101,7 @@ logger 会先触发:`tools/result` 在结果物化过程中发出,发生在 - [构建工具](../user/develop/basic/tool.md):深入了解 `defineTool`,包括呈现和更丰富的 schema。 - [三层能力设计](../user/develop/practice/index.md):harness 如何组织可替换能力。 -- 生成的[服务](../cordis-catalog/services.md)与[事件](../cordis-catalog/events.md)目录:可以注入和监听的所有内容。 +- [子系统页面](../subsystems/core.md)上生成的 `cordis-surface` 区块:可以注入和监听的所有内容,各在其所属页面上。 - [架构](../architecture.md):这些插件所处的系统地图。 [![](https://img.shields.io/badge/powered_by-dsh-4D6BFE?style=flat-square&logo=deepseek&logoColor=white)](https://github.com/deepseek-ai/deepseek-harness-sdk) diff --git a/docs/cordis-tutorial/index.i18n.yaml b/docs/cordis-tutorial/index.i18n.yaml index 496a3fffa5..fa810d635f 100644 --- a/docs/cordis-tutorial/index.i18n.yaml +++ b/docs/cordis-tutorial/index.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/cordis-tutorial/index.md -index.md: a20976706f520416236ca759ee33649d1601eaa9 -index.zh.md: f6989521d4b7dffac6114867cc12371af4e4316f +index.md: 7a0bb6f8c736bf31d655a7763cfb7039c343d1a2 +index.zh.md: e6f6dc0cccef3f44273655b98b695bdc4632e95a diff --git a/docs/cordis-tutorial/index.md b/docs/cordis-tutorial/index.md index a20976706f..7a0bb6f8c7 100644 --- a/docs/cordis-tutorial/index.md +++ b/docs/cordis-tutorial/index.md @@ -6,7 +6,7 @@ Cordis is the plugin framework underneath the DeepSeek Harness SDK: a small runt The audience is agent developers. You do not need deep TypeScript experience; the [TypeScript notes](#typescript-notes) below explain the syntax that may be unfamiliar, and every chapter shows the exact commands and expected output. -If you want the condensed concept reference instead of a walkthrough, read the [Cordis primer](../cordis-primer.md). The exhaustive API reference lives in the generated [events](../cordis-catalog/events.md) and [services](../cordis-catalog/services.md) catalogs and the [Cordis core API](../cordis-catalog/core/context.md) pages. +If you want the condensed concept reference instead of a walkthrough, read the [Cordis primer](../cordis-primer.md). The exhaustive API reference lives in the generated `cordis-surface` regions on the [subsystem pages](../subsystems/core.md) and the [Cordis core API](../cordis-api/context.md) pages. ## Setup diff --git a/docs/cordis-tutorial/index.zh.md b/docs/cordis-tutorial/index.zh.md index f6989521d4..e6f6dc0ccc 100644 --- a/docs/cordis-tutorial/index.zh.md +++ b/docs/cordis-tutorial/index.zh.md @@ -6,7 +6,7 @@ Cordis 是 DeepSeek Harness SDK 底层的插件框架:它是一个小型运行 本教程面向 agent 开发者。你不需要深入掌握 TypeScript;下文的 [TypeScript 说明](#typescript-notes)会解释可能陌生的语法,并且每一章都会给出确切命令和预期输出。 -如果你想阅读精简的概念参考,而不是逐步实践,请参阅 [Cordis 入门](../cordis-primer.md)。详尽的 API 参考见生成的[事件](../cordis-catalog/events.md)与[服务](../cordis-catalog/services.md)目录,以及 [Cordis 核心 API](../cordis-catalog/core/context.md) 页面。 +如果你想阅读精简的概念参考,而不是逐步实践,请参阅 [Cordis 入门](../cordis-primer.md)。详尽的 API 参考见[子系统页面](../subsystems/core.md)上生成的 `cordis-surface` 区块,以及 [Cordis 核心 API](../cordis-api/context.md)页面。 ## 准备工作 diff --git a/docs/graph-atlas.md b/docs/graph-atlas.md index 0c955c9480..5b831520fb 100644 --- a/docs/graph-atlas.md +++ b/docs/graph-atlas.md @@ -3,7 +3,7 @@ # Documentation Graph Index -These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [subsystems/](subsystems/core.md). +These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the [subsystem pages](subsystems/core.md) (types + the generated `cordis-surface` regions) and [tool-catalog.md](tool-catalog.md). The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md). diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index b9a330f21d..432c95e684 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -14,9 +14,9 @@ 依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-spine-demo`,它的职责是组装整套实体主干。 -> This document covers **behavior**; type shapes live in [subsystems/](../subsystems/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). +> This document covers **behavior**; type shapes live in [subsystems/](../subsystems/core.md), the per-event/service reference in the generated regions of [subsystems/](../subsystems/core.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). -本文档描述整体行为逻辑;类型定义存放于 [subsystems/](../subsystems/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各包(package)的对外契约写在相应的 README 中([索引](../../packages/README.md))。 +本文档描述整体行为逻辑;类型定义存放于 [subsystems/](../subsystems/core.md);各类事件、服务的详细参考见 [subsystems/](../subsystems/core.md) 中的生成区块;各包(package)的对外契约写在相应的 README 中([索引](../../packages/README.md))。 ## ② 防御模式规则 diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 04f992e536..04c3a492fe 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -3,7 +3,7 @@ # Session Persistence Event Catalog -Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). +Every event type that can appear in a session's durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit). This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md). diff --git a/docs/subsystems/approval.i18n.yaml b/docs/subsystems/approval.i18n.yaml index a3447601a4..93c56707bf 100644 --- a/docs/subsystems/approval.i18n.yaml +++ b/docs/subsystems/approval.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/approval.md -approval.md: 8f17f98101950e6413d67fb4cce6befc5aede069 -approval.zh.md: c19285572b667512427b71d4af214acbc2baf334 +approval.md: 2045f2073b72fa265728cbf77b673053a2eaf371 +approval.zh.md: c021db14c4925acaeef5d984d9dfa88ad55bd8dd diff --git a/docs/subsystems/approval.md b/docs/subsystems/approval.md index 8f17f98101..2045f2073b 100644 --- a/docs/subsystems/approval.md +++ b/docs/subsystems/approval.md @@ -87,3 +87,85 @@ interface ApprovalRequest { `ctx.approval.request(req)` requires the requesting session to be inside an open turn. It appends `approval/asked`, obtains one outcome, appends the matching `approval/decided`, and resolves with that outcome. The `never` policy is enforced inside the service before waterfall dispatch, so even an answerer registered later with `prepend` cannot bypass it. Answerers return an outcome when they own the request or call `next()` to delegate; the first answer occupies the single decision slot. The audit events are log-only and do not enter the model transcript. Model-visible behavior is the caller's derived tool result plus the current runtime-context snapshot. Service disposal removes its context contribution; answerer listeners are independently effect-bound to their owning plugins. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.approval` — `ApprovalService` + +Approval service that applies session policy before answerers and logs every ask/outcome pair to the requesting session. It exposes deterministic policy changes to the model through the runtime-context snapshot and switch notices. + +```ts cordis-catalog +/** + * Switch one live agent's policy and queue the transition for its next model + * step. Session initialization uses {@link setApprovalPolicy} directly + * because there is no previously visible policy to change. + * @param agent - the live agent whose policy is changing. + * @param policy - the new effective policy. + */ +setPolicy(agent: Agent, policy: ApprovalPolicy): void + +/** + * Ask the composed answerers to decide one readonly same-process request. + * The service borrows the request, agent, session, and live signal directly. + * The request requires an open turn because the audit pair must be enclosed + * by the durable log's commit/replay boundary; an idle ask rejects before + * appending anything. The answerer phase always produces an outcome: an + * aborted signal yields `'cancelled'`, a missing or throwing answerer yields + * `'unavailable'` (fail closed), and a rogue non-vocabulary return value is + * normalized to `'unavailable'`. A failure that prevents either audit append + * from committing still rejects because returning an unlogged decision would + * violate the pair. Session contains post-commit observer failures, so an + * authoritative append cannot reject the request or suppress its matching + * audit event. + * @param req - the pending decision (agent, tool identity, reason, signal). + * @returns the closed outcome; `'allowed-once'` is the only grant. + * @throws when no turn is open or either audit event fails before the session + * append commit point. + */ +async request(req: ApprovalRequest): Promise + +/** + * Read the session override without applying the configured default. + * @param session - session whose log supplies the override. + * @returns the last logged policy, or `undefined` without one. + */ +overrideOf(session: Session): ApprovalPolicy | undefined +``` + +Types: [Agent](core.md) · [Session](session.md) + +Source: [`packages/interaction/user-approval/src/index.ts:193`](../../packages/interaction/user-approval/src/index.ts) + + + +### `approval/*` events + + + +#### `approval/request` — waterfall + +Ask composed answerers for one decision. Return an outcome to claim the request or call `next()`; failure yields the fail-closed default. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + +```ts cordis-catalog +/** + * Ask composed answerers for one decision. Return an outcome to claim the + * request or call `next()`; failure yields the fail-closed default. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @param req - the pending decision (agent, tool identity, reason, signal). + * @mode waterfall + */ +'approval/request'(this: Scoped, req: ApprovalRequest, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/interaction/user-approval/src/index.ts:30`](../../packages/interaction/user-approval/src/index.ts) + diff --git a/docs/subsystems/approval.zh.md b/docs/subsystems/approval.zh.md index c19285572b..c021db14c4 100644 --- a/docs/subsystems/approval.zh.md +++ b/docs/subsystems/approval.zh.md @@ -87,3 +87,85 @@ interface ApprovalRequest { `ctx.approval.request(req)` 要求发起请求的会话处于一个尚未结束的轮次内。它追加 `approval/asked`,获取一个结果,追加对应的 `approval/decided`,然后以该结果完成。`never` 策略在服务内部、waterfall 分发之前强制执行,因此即使后来以 `prepend` 注册的应答者也无法绕过它。应答者在拥有该请求时返回结果,否则调用 `next()` 委托;第一个应答占据唯一的决策槽位。 审计事件仅写入日志,不进入模型 transcript(文本记录)。模型可见的行为是调用方派生的工具结果与当前运行时上下文快照。服务 dispose(资源释放)时会移除其上下文贡献;应答者监听器独立地通过 effect 绑定到其所属插件。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.approval` — `ApprovalService` + +Approval service that applies session policy before answerers and logs every ask/outcome pair to the requesting session. It exposes deterministic policy changes to the model through the runtime-context snapshot and switch notices. + +```ts cordis-catalog +/** + * Switch one live agent's policy and queue the transition for its next model + * step. Session initialization uses {@link setApprovalPolicy} directly + * because there is no previously visible policy to change. + * @param agent - the live agent whose policy is changing. + * @param policy - the new effective policy. + */ +setPolicy(agent: Agent, policy: ApprovalPolicy): void + +/** + * Ask the composed answerers to decide one readonly same-process request. + * The service borrows the request, agent, session, and live signal directly. + * The request requires an open turn because the audit pair must be enclosed + * by the durable log's commit/replay boundary; an idle ask rejects before + * appending anything. The answerer phase always produces an outcome: an + * aborted signal yields `'cancelled'`, a missing or throwing answerer yields + * `'unavailable'` (fail closed), and a rogue non-vocabulary return value is + * normalized to `'unavailable'`. A failure that prevents either audit append + * from committing still rejects because returning an unlogged decision would + * violate the pair. Session contains post-commit observer failures, so an + * authoritative append cannot reject the request or suppress its matching + * audit event. + * @param req - the pending decision (agent, tool identity, reason, signal). + * @returns the closed outcome; `'allowed-once'` is the only grant. + * @throws when no turn is open or either audit event fails before the session + * append commit point. + */ +async request(req: ApprovalRequest): Promise + +/** + * Read the session override without applying the configured default. + * @param session - session whose log supplies the override. + * @returns the last logged policy, or `undefined` without one. + */ +overrideOf(session: Session): ApprovalPolicy | undefined +``` + +Types: [Agent](core.md) · [Session](session.md) + +Source: [`packages/interaction/user-approval/src/index.ts:193`](../../packages/interaction/user-approval/src/index.ts) + + + +### `approval/*` events + + + +#### `approval/request` — waterfall + +Ask composed answerers for one decision. Return an outcome to claim the request or call `next()`; failure yields the fail-closed default. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + +```ts cordis-catalog +/** + * Ask composed answerers for one decision. Return an outcome to claim the + * request or call `next()`; failure yields the fail-closed default. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @param req - the pending decision (agent, tool identity, reason, signal). + * @mode waterfall + */ +'approval/request'(this: Scoped, req: ApprovalRequest, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/interaction/user-approval/src/index.ts:30`](../../packages/interaction/user-approval/src/index.ts) + diff --git a/docs/subsystems/bash.i18n.yaml b/docs/subsystems/bash.i18n.yaml index 30e15dbacc..f6ed20cace 100644 --- a/docs/subsystems/bash.i18n.yaml +++ b/docs/subsystems/bash.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/bash.md -bash.md: f83a133c8e049bc111cfabd2b59def18a1b2d607 -bash.zh.md: 27ca9f6e5a8e05cd4ffa86e72d433b6f22005f09 +# pnpm run verify-translation-pairing --write docs/subsystems/bash.md +bash.md: dd7649ce46bea5642270fa553eb3d82f2dc33e9b +bash.zh.md: bba9ece2955cee66e941aa878299cff6c06a97a0 diff --git a/docs/subsystems/bash.md b/docs/subsystems/bash.md index f83a133c8e..dd7649ce46 100644 --- a/docs/subsystems/bash.md +++ b/docs/subsystems/bash.md @@ -219,3 +219,85 @@ interface BashProcessRead { ## The service `BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns command defaulting, timeout/abort classification, the terminal environment, and the background read merge; process groups, bounded collectors, spill files, credential scrubbing, and disposal quiescence are the [subprocess service](subprocess.md)'s. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic task runtime](tasks.md). `dsh-bash` owns the shell tools' shared exit-status contract: the exported `parseExitStatus`/`ParsedExitStatus` inverts the `[exit code: N]` / `[killed by signal: X]` markers `dsh-tool-bash`'s `renderResult` and `dsh-tool-pwsh`'s `renderPwshResult` append, and both tools' `presentResult` use it to split the rendered text into the terminal card's output body and its exit-status pill. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.bash` — `BashExecutor` (abstract seam) + +Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult. +- start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr. +- BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files. +- A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload. + +```ts cordis-catalog +/** + * Apply implementation-owned defaults and caps to a request before execution. + * @param request - the caller's request; omitted fields get this + * implementation's defaults, capped fields are clamped. + * @returns the fully-specified spec to hand to {@link run}/{@link start}. + */ +abstract resolve(request: BashExecRequest): BashExecSpec + +/** + * Run a command in the foreground; resolves when it finishes. + * @param spec - a resolved spec from {@link resolve}, never a raw request. + * @returns the outcome; nonzero exits, timeout kills, and abort kills + * resolve with a descriptive result rather than reject. + */ +abstract run(spec: BashExecSpec): Promise + +/** + * Start a background process and return its handle immediately. + * @param spec - a resolved spec from {@link resolve}, never a raw request. + * @returns the live process handle (reads, kill, quiescence promise). + */ +abstract start(spec: BashExecSpec): BashProcess +``` + +Source: [`packages/bash/bash/src/index.ts:53`](../../packages/bash/bash/src/index.ts) + + + +### `ctx.bashEnv` — `BashEnvRegistry` + +Registry (`ctx.bashEnv`) for trusted, per-execution `DSH_*` variables. The namespace is rebuilt for every model shell call: ambient `DSH_*` values are discarded by the executor, then the registry's current snapshot is injected. Built-in shell facts remain owned by the registry itself while plugins can register additional, enumerable facts with effect-scoped disposal. + +```ts cordis-catalog +/** + * Register one environment contributor. Names and keys are unique; built-in + * keys are reserved. Registration is disposed with the calling plugin fiber. + * @param contributor - declared key ownership and per-execution resolver. + * @returns the disposer that unregisters the contribution. + */ +register(contributor: BashEnvContributor): () => void + +/** + * Build the trusted `DSH_*` snapshot for one shell tool execution. + * @param execution - the current tool execution. + * @returns an immutable environment overlay containing built-ins and current contributions. + */ +collect(execution: ToolExecution): DshEnvironment + +/** + * Enumerate plugin-contributed variables without executing their resolvers. + * @returns declarations sorted by environment variable name. + */ +list(): BashEnvVariableInfo[] +``` + +Types: [DshEnvironment](subprocess.md) · [ToolExecution](tools.md) + +Source: [`packages/bash/bash-env/src/index.ts:89`](../../packages/bash/bash-env/src/index.ts) + diff --git a/docs/subsystems/bash.zh.md b/docs/subsystems/bash.zh.md index 27ca9f6e5a..bba9ece295 100644 --- a/docs/subsystems/bash.zh.md +++ b/docs/subsystems/bash.zh.md @@ -219,3 +219,85 @@ interface BashProcessRead { ## 服务 `BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。`dsh-bash` 拥有 shell 工具共享的退出状态契约:导出的 `parseExitStatus`/`ParsedExitStatus` 是 `dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 所追加的 `[exit code: N]` / `[killed by signal: X]` 标记的逆解析,两个工具的 `presentResult` 都用它把渲染文本拆分为 terminal 卡的输出正文与退出状态 pill。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.bash` — `BashExecutor` (abstract seam) + +Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.bash` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult. +- start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr. +- BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files. +- A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload. + +```ts cordis-catalog +/** + * Apply implementation-owned defaults and caps to a request before execution. + * @param request - the caller's request; omitted fields get this + * implementation's defaults, capped fields are clamped. + * @returns the fully-specified spec to hand to {@link run}/{@link start}. + */ +abstract resolve(request: BashExecRequest): BashExecSpec + +/** + * Run a command in the foreground; resolves when it finishes. + * @param spec - a resolved spec from {@link resolve}, never a raw request. + * @returns the outcome; nonzero exits, timeout kills, and abort kills + * resolve with a descriptive result rather than reject. + */ +abstract run(spec: BashExecSpec): Promise + +/** + * Start a background process and return its handle immediately. + * @param spec - a resolved spec from {@link resolve}, never a raw request. + * @returns the live process handle (reads, kill, quiescence promise). + */ +abstract start(spec: BashExecSpec): BashProcess +``` + +Source: [`packages/bash/bash/src/index.ts:53`](../../packages/bash/bash/src/index.ts) + + + +### `ctx.bashEnv` — `BashEnvRegistry` + +Registry (`ctx.bashEnv`) for trusted, per-execution `DSH_*` variables. The namespace is rebuilt for every model shell call: ambient `DSH_*` values are discarded by the executor, then the registry's current snapshot is injected. Built-in shell facts remain owned by the registry itself while plugins can register additional, enumerable facts with effect-scoped disposal. + +```ts cordis-catalog +/** + * Register one environment contributor. Names and keys are unique; built-in + * keys are reserved. Registration is disposed with the calling plugin fiber. + * @param contributor - declared key ownership and per-execution resolver. + * @returns the disposer that unregisters the contribution. + */ +register(contributor: BashEnvContributor): () => void + +/** + * Build the trusted `DSH_*` snapshot for one shell tool execution. + * @param execution - the current tool execution. + * @returns an immutable environment overlay containing built-ins and current contributions. + */ +collect(execution: ToolExecution): DshEnvironment + +/** + * Enumerate plugin-contributed variables without executing their resolvers. + * @returns declarations sorted by environment variable name. + */ +list(): BashEnvVariableInfo[] +``` + +Types: [DshEnvironment](subprocess.md) · [ToolExecution](tools.md) + +Source: [`packages/bash/bash-env/src/index.ts:89`](../../packages/bash/bash-env/src/index.ts) + diff --git a/docs/subsystems/client-modules.i18n.yaml b/docs/subsystems/client-modules.i18n.yaml index f0df40a866..ae61603169 100644 --- a/docs/subsystems/client-modules.i18n.yaml +++ b/docs/subsystems/client-modules.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md -client-modules.md: 5dc89a87db7e79e4a297002fa059fdeffcceda20 -client-modules.zh.md: b3aa9c2aecf8e051a41b7b05f05d364b9ff8d2a6 +client-modules.md: 19bcc6304e9d917c3950fd9857e7b2c2b3c16e73 +client-modules.zh.md: b0ad293cad2f67de1982ef271308b2a7f06ebaf4 diff --git a/docs/subsystems/client-modules.md b/docs/subsystems/client-modules.md index 5dc89a87db..19bcc6304e 100644 --- a/docs/subsystems/client-modules.md +++ b/docs/subsystems/client-modules.md @@ -58,6 +58,61 @@ Package metadata — including the negative "not a client package" verdict — i ## The service -`ClientModuleHostService` (`ctx.clientModuleHost`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxclientmodulehost--clientmodulehostservice). `graph()` returns the current composed graph (a stable object between changes) and `clientPath(id)` the bundle's absolute path. `rebuilt(id)` is the only entry point through which bundle content reaches the graph: it re-hashes the file, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush. +`ClientModuleHostService` (`ctx.clientModuleHost`, defined in [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts)) exposes reads and the rebuild face; signatures are in the generated [service catalog](#ctxclientmodulehost--clientmodulehostservice). `graph()` returns the current composed graph (a stable object between changes) and `clientPath(id)` the bundle's absolute path. `rebuilt(id)` is the only entry point through which bundle content reaches the graph: it re-hashes the file, and only a real rev change recomposes the graph and notifies. `onRebuilt` fires per changed bundle with the new rev; `onGraphChanged` fires after any flush that recomposed the graph (row added or removed, or a rebuilt rev change) and is pull-model — listeners re-read `graph()`. Both notification paths contain listener exceptions so one throwing subscriber cannot skip later subscribers or kill whatever triggered the flush. In development, [dsh-client-hmr](../../packages/client/hmr/README.md) is the registry's watch driver: its node half stat-polls every graph row's bundle from a synchronously captured baseline, calls `rebuilt(id)` on change, resyncs its watch set through `onGraphChanged`, and broadcasts rev changes to the browser half over SSE. Production graphs omit the HMR row entirely; the module host itself never watches files. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.clientModuleHost` — `ClientModuleHostService` + +The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). + +```ts cordis-catalog +/** + * Current composed entry graph (stable object between changes). + * @returns the graph served as `window.__DSH_BOOT__`. + */ +graph(): WebBootGraph + +/** + * Absolute path of an entry's client bundle. + * @param id - entry id (package name). + * @returns the path, or undefined for an unknown id. + */ +clientPath(id: string): string | undefined + +/** + * Re-hash one bundle (the HMR watch's registration hook — the only entry + * point through which bundle content changes reach the graph). + * @param id - entry id (package name). + * @returns the new rev, or undefined for an unknown id. + */ +rebuilt(id: string): string | undefined + +/** + * Subscribe to bundle rebuilds; fires only when the re-hash changed the rev. + * @param listener - receives the entry id and its new bundle rev. + * @returns the unsubscriber. + */ +onRebuilt(listener: (id: string, rev: string) => void): () => void + +/** + * Fires after any flush that recomposed the graph (row added/removed, or a + * rebuilt rev change). Pull model: listeners re-read {@link graph}. + * @param listener - notified with no payload. + * @returns the unsubscriber. + */ +onGraphChanged(listener: () => void): () => void +``` + +Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts) + diff --git a/docs/subsystems/client-modules.zh.md b/docs/subsystems/client-modules.zh.md index b3aa9c2aec..b0ad293cad 100644 --- a/docs/subsystems/client-modules.zh.md +++ b/docs/subsystems/client-modules.zh.md @@ -58,6 +58,61 @@ interface WebBootGraph { ## 服务 -`ClientModuleHostService`(`ctx.clientModuleHost`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](../cordis-catalog/services.md#ctxclientmodulehost--clientmodulehostservice)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回该 bundle 的绝对路径。`rebuilt(id)` 是 bundle 内容到达图的唯一入口:它对文件重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。 +`ClientModuleHostService`(`ctx.clientModuleHost`,定义于 [`packages/client/modules/src/index.ts`](../../packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodulehost--clientmodulehostservice)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回该 bundle 的绝对路径。`rebuilt(id)` 是 bundle 内容到达图的唯一入口:它对文件重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。 开发环境下,[dsh-client-hmr](../../packages/client/hmr/README.md) 是注册表的监视驱动:它的 Node 半从同步取得的基线出发,对图中每一行的 bundle 做 stat 轮询,变化时调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。生产环境的图完全不含 HMR(热模块替换)行;模块宿主自身从不监视文件。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.clientModuleHost` — `ClientModuleHostService` + +The web plugin table service: incremental dshClient scan + wire composition + bundle route + index tap. Construction runs the activation scan synchronously — a malformed declaration or missing bundle among the already-loaded entries aggregates into one loud throw (FAILED fiber; the boot activation audit reports it). + +```ts cordis-catalog +/** + * Current composed entry graph (stable object between changes). + * @returns the graph served as `window.__DSH_BOOT__`. + */ +graph(): WebBootGraph + +/** + * Absolute path of an entry's client bundle. + * @param id - entry id (package name). + * @returns the path, or undefined for an unknown id. + */ +clientPath(id: string): string | undefined + +/** + * Re-hash one bundle (the HMR watch's registration hook — the only entry + * point through which bundle content changes reach the graph). + * @param id - entry id (package name). + * @returns the new rev, or undefined for an unknown id. + */ +rebuilt(id: string): string | undefined + +/** + * Subscribe to bundle rebuilds; fires only when the re-hash changed the rev. + * @param listener - receives the entry id and its new bundle rev. + * @returns the unsubscriber. + */ +onRebuilt(listener: (id: string, rev: string) => void): () => void + +/** + * Fires after any flush that recomposed the graph (row added/removed, or a + * rebuilt rev change). Pull model: listeners re-read {@link graph}. + * @param listener - notified with no payload. + * @returns the unsubscriber. + */ +onGraphChanged(listener: () => void): () => void +``` + +Source: [`packages/client/modules/src/index.ts:184`](../../packages/client/modules/src/index.ts) + diff --git a/docs/subsystems/code-runtime.i18n.yaml b/docs/subsystems/code-runtime.i18n.yaml index 02bbc9587a..05e7e67ed1 100644 --- a/docs/subsystems/code-runtime.i18n.yaml +++ b/docs/subsystems/code-runtime.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/code-runtime.md -code-runtime.md: fbce7d812b7609716fb43ae01610253008e0a92c -code-runtime.zh.md: 700146cfaa9cfab37ec4d85e550020acf1d9f294 +# pnpm run verify-translation-pairing --write docs/subsystems/code-runtime.md +code-runtime.md: 12779f1aafc8930410ae0d695e3f617caafcf607 +code-runtime.zh.md: de563dd3430348f5cf3675d4bf124adeba170559 diff --git a/docs/subsystems/code-runtime.md b/docs/subsystems/code-runtime.md index fbce7d812b..12779f1aaf 100644 --- a/docs/subsystems/code-runtime.md +++ b/docs/subsystems/code-runtime.md @@ -159,3 +159,33 @@ interface CodeRunFailure { ## The service `CodeRuntime` (`ctx.codeRuntime`, abstract — defined in [`packages/code-runtime/code-runtime/src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts)) is `run(request)` plus two readonly descriptors: `language` (what the program must be written in — `'typescript'` and `'python'` are the well-known values, those `dsh-tools` presents, and only `'typescript'` has a published backend; a consumer generating language-specific presentation switches on it and fails loud on one it cannot present) and `isolation` (the execution substrate — `'worker-thread'`, `'process'`, `'container'`; a diagnostic label, **not a security claim**). Implementations must keep runs isolated from each other (no cross-run state) and dispose to quiescence: in-flight runs are terminated and awaited before teardown completes. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.codeRuntime` — `CodeRuntime` (abstract seam) + +Registers one `ctx.codeRuntime` implementation. Program, budget, abort, and substrate failures resolve in CodeRunResult; only seam misuse rejects. Implementations bridge structured-cloneable bindings, materialize each declared namespace rejection class, treat programs as hostile peers, isolate runs from one another, and terminate and await in-flight runs during disposal. + +```ts cordis-catalog +/** + * Execute one program against the request's bindings and capture what it + * emitted. See the class doc for the resolution contract (error is a result + * field; rejection means seam misuse only). + * @param request - the program, its bindings, and the abort signal; the + * request carries everything the runtime acts on, with no hidden defaults. + * @returns the run's outcome: completion value (when transferable), the + * ordered log capture, and the failure (if any). + */ +abstract run(request: CodeRunRequest): Promise +``` + +Source: [`packages/code-runtime/code-runtime/src/index.ts:104`](../../packages/code-runtime/code-runtime/src/index.ts) + diff --git a/docs/subsystems/code-runtime.zh.md b/docs/subsystems/code-runtime.zh.md index 700146cfaa..de563dd343 100644 --- a/docs/subsystems/code-runtime.zh.md +++ b/docs/subsystems/code-runtime.zh.md @@ -159,3 +159,33 @@ interface CodeRunFailure { ## 服务 `CodeRuntime`(`ctx.codeRuntime`,抽象服务,定义于 [`packages/code-runtime/code-runtime/src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts))由 `run(request)` 加两个只读描述符组成:`language`(程序必须使用的语言,已知值为 `'typescript'` 与 `'python'`,即 `dsh-tools` 能呈现的那些,其中只有 `'typescript'` 有已发布的后端;生成语言相关展示的消费方据此切换,遇到无法展示的语言时应显式报错)和 `isolation`(执行基底,`'worker-thread'`、`'process'`、`'container'`;仅为诊断标签,**不构成安全承诺**)。实现必须保证各次运行彼此隔离(无跨运行状态),并在 dispose(资源释放)时等待系统完全停稳:teardown 要等到所有进行中的运行均已终止并结算后才完成。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.codeRuntime` — `CodeRuntime` (abstract seam) + +Registers one `ctx.codeRuntime` implementation. Program, budget, abort, and substrate failures resolve in CodeRunResult; only seam misuse rejects. Implementations bridge structured-cloneable bindings, materialize each declared namespace rejection class, treat programs as hostile peers, isolate runs from one another, and terminate and await in-flight runs during disposal. + +```ts cordis-catalog +/** + * Execute one program against the request's bindings and capture what it + * emitted. See the class doc for the resolution contract (error is a result + * field; rejection means seam misuse only). + * @param request - the program, its bindings, and the abort signal; the + * request carries everything the runtime acts on, with no hidden defaults. + * @returns the run's outcome: completion value (when transferable), the + * ordered log capture, and the failure (if any). + */ +abstract run(request: CodeRunRequest): Promise +``` + +Source: [`packages/code-runtime/code-runtime/src/index.ts:104`](../../packages/code-runtime/code-runtime/src/index.ts) + diff --git a/docs/subsystems/commands.i18n.yaml b/docs/subsystems/commands.i18n.yaml index a741971a9a..3bd5420573 100644 --- a/docs/subsystems/commands.i18n.yaml +++ b/docs/subsystems/commands.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/commands.md -commands.md: 83ce9c2498df58110d9a9c2e7d5163a9df813e1c -commands.zh.md: c486f480177c7ff8b35cc846beafac009cea42bb +commands.md: c1ab6d9782f515321104d0cf4d9e037c6a68a18d +commands.zh.md: 3b3d8def042b107c5098e76c511424f023975e77 diff --git a/docs/subsystems/commands.md b/docs/subsystems/commands.md index 83ce9c2498..c1ab6d9782 100644 --- a/docs/subsystems/commands.md +++ b/docs/subsystems/commands.md @@ -97,3 +97,89 @@ interface ParsedCommand { readonly rawInput: string } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.commands` — `CommandService` + +Human-command registry. Plain-context definitions are global; definitions registered through a command-injected child of an agent context shadow globals for that agent. + +```ts cordis-catalog +/** + * Register a global or calling-agent-scoped command. + * @param definition - discovery metadata and direct UI handler. + * @returns the exact effect disposer that unregisters this definition. + */ +register(definition: CommandDefinition): () => void + +/** + * List the effective immutable command descriptors for one agent. + * @param agent - exact receiving agent and scoped-layer key. + * @returns name-sorted descriptors after scoped shadowing. + */ +list(agent: Agent): readonly CommandDescriptor[] + +/** + * Resolve one effective command definition. + * @param agent - exact receiving agent and scoped-layer key. + * @param name - command name without a slash. + * @returns the scoped shadow or global definition. + */ +find(agent: Agent, name: string): CommandDefinition | undefined + +/** + * Parse and execute a known command without sending it to the model. + * + * A resolved command's lifecycle is logged: `command/run` is appended + * before the handler is invoked and `command/done` after settlement (a + * thrown or aborted handler settles as `kind: 'error'`). Both are direct + * log-only appends — no turn wraps them, and persistence drains them at + * ordinary checkpoints. Admission misses (syntax or unknown name) log + * nothing — they never entered a handler. A `command/run` append failure + * fails the execution loud; a `command/done` append failure on the + * handler-failure path is contained so the handler's own error stays the + * reported failure. + * + * @param agent - exact receiving agent. + * @param line - complete slash-command line. + * @param signal - cancellation signal owned by the UI request. + * @returns the settled execution (result + lifecycle pairing id), or + * `undefined` when syntax or name does not resolve. + */ +async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise +``` + +Types: [Agent](core.md) + +Source: [`packages/interaction/commands/src/index.ts:305`](../../packages/interaction/commands/src/index.ts) + + + +### `commands/*` events + + + +#### `commands/change` — emit + +A command was registered or unregistered. This is an unfiltered registry notification because a global or scoped change may affect any UI view. Observer failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * A command was registered or unregistered. This is an unfiltered registry + * notification because a global or scoped change may affect any UI view. + * Observer failures are contained and cannot veto the registry mutation. + * @mode emit + */ +'commands/change'(): void +``` + +Source: [`packages/interaction/commands/src/index.ts:172`](../../packages/interaction/commands/src/index.ts) + diff --git a/docs/subsystems/commands.zh.md b/docs/subsystems/commands.zh.md index c486f48017..3b3d8def04 100644 --- a/docs/subsystems/commands.zh.md +++ b/docs/subsystems/commands.zh.md @@ -97,3 +97,89 @@ interface ParsedCommand { readonly rawInput: string } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.commands` — `CommandService` + +Human-command registry. Plain-context definitions are global; definitions registered through a command-injected child of an agent context shadow globals for that agent. + +```ts cordis-catalog +/** + * Register a global or calling-agent-scoped command. + * @param definition - discovery metadata and direct UI handler. + * @returns the exact effect disposer that unregisters this definition. + */ +register(definition: CommandDefinition): () => void + +/** + * List the effective immutable command descriptors for one agent. + * @param agent - exact receiving agent and scoped-layer key. + * @returns name-sorted descriptors after scoped shadowing. + */ +list(agent: Agent): readonly CommandDescriptor[] + +/** + * Resolve one effective command definition. + * @param agent - exact receiving agent and scoped-layer key. + * @param name - command name without a slash. + * @returns the scoped shadow or global definition. + */ +find(agent: Agent, name: string): CommandDefinition | undefined + +/** + * Parse and execute a known command without sending it to the model. + * + * A resolved command's lifecycle is logged: `command/run` is appended + * before the handler is invoked and `command/done` after settlement (a + * thrown or aborted handler settles as `kind: 'error'`). Both are direct + * log-only appends — no turn wraps them, and persistence drains them at + * ordinary checkpoints. Admission misses (syntax or unknown name) log + * nothing — they never entered a handler. A `command/run` append failure + * fails the execution loud; a `command/done` append failure on the + * handler-failure path is contained so the handler's own error stays the + * reported failure. + * + * @param agent - exact receiving agent. + * @param line - complete slash-command line. + * @param signal - cancellation signal owned by the UI request. + * @returns the settled execution (result + lifecycle pairing id), or + * `undefined` when syntax or name does not resolve. + */ +async execute( agent: Agent, line: string, signal: AbortSignal, ): Promise +``` + +Types: [Agent](core.md) + +Source: [`packages/interaction/commands/src/index.ts:305`](../../packages/interaction/commands/src/index.ts) + + + +### `commands/*` events + + + +#### `commands/change` — emit + +A command was registered or unregistered. This is an unfiltered registry notification because a global or scoped change may affect any UI view. Observer failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * A command was registered or unregistered. This is an unfiltered registry + * notification because a global or scoped change may affect any UI view. + * Observer failures are contained and cannot veto the registry mutation. + * @mode emit + */ +'commands/change'(): void +``` + +Source: [`packages/interaction/commands/src/index.ts:172`](../../packages/interaction/commands/src/index.ts) + diff --git a/docs/subsystems/compaction.i18n.yaml b/docs/subsystems/compaction.i18n.yaml index 7dc00cebfc..5481b2feb1 100644 --- a/docs/subsystems/compaction.i18n.yaml +++ b/docs/subsystems/compaction.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/compaction.md -compaction.md: f1df5b83bd43136af60988dabd9dc68fe32467a2 -compaction.zh.md: 52540250d466f81f51cf7c681bb6f1436e29a12d +compaction.md: 126c6c7c7df125cf991ea074e0165481c09455dc +compaction.zh.md: 9fb01a41a92367b9bb3f4586f35b5d0baf613bbc diff --git a/docs/subsystems/compaction.md b/docs/subsystems/compaction.md index f1df5b83bd..126c6c7c7d 100644 --- a/docs/subsystems/compaction.md +++ b/docs/subsystems/compaction.md @@ -112,3 +112,119 @@ interface PruneResult { readonly charsRemoved: number } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.compact` — `CompactService` (abstract seam) + +Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. The replacement user message uses COMPACT_CHECKPOINT_SOURCE so consumers recognize it independently of the backend. Load one implementation per context as `ctx.compact`. + +```ts cordis-catalog +/** + * Consider automatic compaction for one explicit trigger. Pressure policy + * uses the latest durable routed request, while context-overflow policy may + * force a useful balanced reduction even below the normal threshold. Return + * `null` when no safe range can be compacted. A single oversized retained + * unit or request envelope cannot be repaired through surface compaction. + * + * @param agent - agent context owning the session surface and routing options. + * @param trigger - normal pressure or provider-confirmed context overflow. + * @param signal - cancellation signal; model-backed implementations must forward it. + * @returns the compaction result, or `null` if no compaction was needed. + */ +abstract compactIfNeeded( agent: CompactAgentContext, trigger: CompactionTrigger, signal: AbortSignal, ): Promise + +/** + * Explicitly compact useful history even below automatic pressure thresholds. + * Implementations synchronously start an idle task before any asynchronous + * work, select a useful range without writing on a no-op, then + * append a standalone `compact/start` before summarization. That durable + * marker is the compaction lock until one `compact/end` attempt. Later waking + * prompts remain accepted in FIFO order and start only after the optional + * durability checkpoint and idle-task settlement. Context injected while the + * summary runs may sit between the marker pair; only the selected span must + * remain stable. + * + * @param agent - idle agent whose durable history should be compacted. + * @param signal - cancellation scoped to this compaction request. + * @returns the compaction result, or `null` when no safe useful range exists. + * @throws {@link ManualCompactionError} for expected busy, agent-cancellation, + * changed-span, summarization/shrink, commit-stage, or persistence failures; + * an aborted request preserves its exact abort reason. Failed attempts remain + * visible in the log. + */ +abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, ): Promise + +/** + * Forcibly compact a range of surface nodes into a single summary node. + * `start` and `end` name an inclusive span by surface position, not numeric seq + * order; replacements can make visible seqs non-monotonic. Both edges must be + * balanced so assistant tool calls remain paired with their results. A model- + * backed implementation forwards cancellation and rejects active, missing, + * reversed, or unbalanced ranges. The target session is `agent.session`. + * Its replacement user message must use {@link COMPACT_CHECKPOINT_SOURCE}. + * Use {@link toolPairingBalancedBefore} and {@link toolPairingBalancedAfter} + * for the edge checks. + * + * @param start - first surface seq, inclusive. + * @param end - last surface seq, inclusive. + * @param agent - context whose session is mutated and whose routing options guide summarization. + * @param signal - optional cancellation; model-backed implementations must forward it. + * @throws when compaction is active or the range is missing, reversed, or unbalanced. + * @returns the appended event seqs, summary, replaced range, and token accounting. + */ +abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise +``` + +Source: [`packages/compact/compact/src/index.ts:93`](../../packages/compact/compact/src/index.ts) + + + +### `ctx.toolResultPrune` — `ToolResultPruneService` + +Deterministic head/middle/tail pruning for current tool-result surface nodes. + +```ts cordis-catalog +/** + * Measure text content in Unicode code points; non-text blocks cost zero. + * @param blocks - tool-result content to measure. + * @returns total Unicode code points across text blocks. + */ +measureContent(blocks: readonly ContentBlock[]): number + +/** + * Replace an over-budget text middle while retaining rich-block order. + * Text slicing is by Unicode code point, not UTF-16 code unit, so a retained + * boundary cannot split a surrogate pair. Grapheme clusters may still split. + * @param blocks - original tool-result content. + * @returns pruned content, or `null` when the text is within budget. + */ +pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null + +/** + * Prune every over-budget tool result from one stable current-surface snapshot. + * Each replacement preserves the complete event data except for `content`, + * points at the shadowed node for durable provenance and replay, and is + * immediately preceded by a `compact/prune` shadow-price event pricing the + * shadowed node through the injected token meter, so pure consumers can + * subtract it without per-node state. + * @param session - session whose current surface is rewritten. + * @returns landed replacements and aggregate Unicode-code-point savings. + * @throws when the session rejects a replacement; replacements committed + * earlier in the pass remain durable. + */ +pruneSession(session: Session): PruneResult +``` + +Types: [ContentBlock](core.md) · [Session](session.md) + +Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) + diff --git a/docs/subsystems/compaction.zh.md b/docs/subsystems/compaction.zh.md index 52540250d4..9fb01a41a9 100644 --- a/docs/subsystems/compaction.zh.md +++ b/docs/subsystems/compaction.zh.md @@ -112,3 +112,119 @@ interface PruneResult { readonly charsRemoved: number } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.compact` — `CompactService` (abstract seam) + +Abstract compaction service. Implementations own trigger policy, retention, and summarization, and may consume a separate measurement service. A successful run replaces the selected surface span with one summary node and prevents concurrent compaction of the same session. The replacement user message uses COMPACT_CHECKPOINT_SOURCE so consumers recognize it independently of the backend. Load one implementation per context as `ctx.compact`. + +```ts cordis-catalog +/** + * Consider automatic compaction for one explicit trigger. Pressure policy + * uses the latest durable routed request, while context-overflow policy may + * force a useful balanced reduction even below the normal threshold. Return + * `null` when no safe range can be compacted. A single oversized retained + * unit or request envelope cannot be repaired through surface compaction. + * + * @param agent - agent context owning the session surface and routing options. + * @param trigger - normal pressure or provider-confirmed context overflow. + * @param signal - cancellation signal; model-backed implementations must forward it. + * @returns the compaction result, or `null` if no compaction was needed. + */ +abstract compactIfNeeded( agent: CompactAgentContext, trigger: CompactionTrigger, signal: AbortSignal, ): Promise + +/** + * Explicitly compact useful history even below automatic pressure thresholds. + * Implementations synchronously start an idle task before any asynchronous + * work, select a useful range without writing on a no-op, then + * append a standalone `compact/start` before summarization. That durable + * marker is the compaction lock until one `compact/end` attempt. Later waking + * prompts remain accepted in FIFO order and start only after the optional + * durability checkpoint and idle-task settlement. Context injected while the + * summary runs may sit between the marker pair; only the selected span must + * remain stable. + * + * @param agent - idle agent whose durable history should be compacted. + * @param signal - cancellation scoped to this compaction request. + * @returns the compaction result, or `null` when no safe useful range exists. + * @throws {@link ManualCompactionError} for expected busy, agent-cancellation, + * changed-span, summarization/shrink, commit-stage, or persistence failures; + * an aborted request preserves its exact abort reason. Failed attempts remain + * visible in the log. + */ +abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, ): Promise + +/** + * Forcibly compact a range of surface nodes into a single summary node. + * `start` and `end` name an inclusive span by surface position, not numeric seq + * order; replacements can make visible seqs non-monotonic. Both edges must be + * balanced so assistant tool calls remain paired with their results. A model- + * backed implementation forwards cancellation and rejects active, missing, + * reversed, or unbalanced ranges. The target session is `agent.session`. + * Its replacement user message must use {@link COMPACT_CHECKPOINT_SOURCE}. + * Use {@link toolPairingBalancedBefore} and {@link toolPairingBalancedAfter} + * for the edge checks. + * + * @param start - first surface seq, inclusive. + * @param end - last surface seq, inclusive. + * @param agent - context whose session is mutated and whose routing options guide summarization. + * @param signal - optional cancellation; model-backed implementations must forward it. + * @throws when compaction is active or the range is missing, reversed, or unbalanced. + * @returns the appended event seqs, summary, replaced range, and token accounting. + */ +abstract compactRegion( start: number, end: number, agent: CompactAgentContext, signal?: AbortSignal, ): Promise +``` + +Source: [`packages/compact/compact/src/index.ts:93`](../../packages/compact/compact/src/index.ts) + + + +### `ctx.toolResultPrune` — `ToolResultPruneService` + +Deterministic head/middle/tail pruning for current tool-result surface nodes. + +```ts cordis-catalog +/** + * Measure text content in Unicode code points; non-text blocks cost zero. + * @param blocks - tool-result content to measure. + * @returns total Unicode code points across text blocks. + */ +measureContent(blocks: readonly ContentBlock[]): number + +/** + * Replace an over-budget text middle while retaining rich-block order. + * Text slicing is by Unicode code point, not UTF-16 code unit, so a retained + * boundary cannot split a surrogate pair. Grapheme clusters may still split. + * @param blocks - original tool-result content. + * @returns pruned content, or `null` when the text is within budget. + */ +pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null + +/** + * Prune every over-budget tool result from one stable current-surface snapshot. + * Each replacement preserves the complete event data except for `content`, + * points at the shadowed node for durable provenance and replay, and is + * immediately preceded by a `compact/prune` shadow-price event pricing the + * shadowed node through the injected token meter, so pure consumers can + * subtract it without per-node state. + * @param session - session whose current surface is rewritten. + * @returns landed replacements and aggregate Unicode-code-point savings. + * @throws when the session rejects a replacement; replacements committed + * earlier in the pass remain durable. + */ +pruneSession(session: Session): PruneResult +``` + +Types: [ContentBlock](core.md) · [Session](session.md) + +Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) + diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index a2af261bc6..48d6fc9c71 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: 3a8ccbef4163eabba1cd287284eda68135f2db13 -core.zh.md: 0cd6a57971ee98c75b52dd45b6f648632dab8a90 +core.md: 7e62de88519e5c0bf3a1a0b94de93635767f9f7b +core.zh.md: ef5b70ae336d55fd25c2251372705912473d07f7 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 3a8ccbef41..7e62de8851 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -53,9 +53,10 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t | [storage.md](storage.md) | the storage subsystem: the backend seam (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | | [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 | +| [session-projection.md](session-projection.md) | the projection seam: `SessionProjectionMap`, the pure `ProjectionDefinition` unit, `ProjectionSnapshot`'s consistent cut, the change feed | | [telemetry.md](telemetry.md) | the outbound reporting seam: `TelemetryRecord`/`TelemetrySeverity`, the `TelemetryBackend` contract, the `telemetry/record` redact waterfall | -> Type declarations and their JSDoc on these pages are source-equivalent and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Ordinary blocks preserve complete declarations; `public-api` blocks preserve body-stripped public class declarations. Cordis services use the generated [service catalog](../cordis-catalog/services.md). +> Type declarations and their JSDoc on these pages are source-equivalent and drift-checked by `pnpm run verify-type-equiv` (see [development.md](../development.md#documenting-types-verbatim-ts-type-equiv)). Ordinary blocks preserve complete declarations; `public-api` blocks preserve body-stripped public class declarations. Cordis services use the generated [service catalog](#cordis-surface). ## The `…Map → derived-union` pattern @@ -780,3 +781,546 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact' The one pipeline-authoring type that is core: what every registered tool *is* — a model-facing `ToolSchema` plus an `execute` function and optional final-content and UI callbacks. A tool author rarely constructs it by hand (the `defineTool` DSL builds it with typed args), but it is the contract the registry holds and the loop dispatches through. Its full fields, the `defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` typed schema DSL, the `ToolExecution`/`ToolExecutionResult` waterfall shapes, and the tool-presentation UI vocabulary are on **[tools.md](tools.md)**. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.agentLoop` — `AgentLoop` + +Concrete agent factory and driver service. + +```ts cordis-catalog +/** + * Create an agent and session under one caller-supplied identity, owned by + * the accessing fiber. Constructor-driven config calls mint a fresh combined + * id before entering this boundary. + * @param id - shared agent/session identity. + * @param options - concrete loop options. + * @param meta - optional fresh-session workspace metadata. + * @returns the published running agent. + */ +create(id: SessionId, options: AgentOptions = {}, meta: Pick = {}): Agent + +/** + * Create an owned agent on a caller-supplied session id. + * @param ownerCtx - caller context that structurally owns the lifecycle. + * @param options - identities, session seed/metadata, loop options, setup, and cancellation. + * @returns the published handle. + */ +async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise + +/** + * Resume an owned agent from the configured persistence service. + * @param ownerCtx - caller context that owns load, setup, and the live lifecycle. + * @param options - persisted identity, loop options, setup, and cancellation. + * @returns the published handle. + */ +async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise +``` + +Types: [SessionHeader](persistence.md) + +Source: [`packages/core/agent-loop/src/index.ts:277`](../../packages/core/agent-loop/src/index.ts) + + + +### `ctx.agents` — `AgentRegistry` + +Agent service (`ctx.agents`): tracks live agents and carries the initiating Agent through one process-local asynchronous driver chain. Agent *creation* is provided by whichever plugin implements the AgentFactory (`@deepseek-ai/dsh-agent-loop`), registered via setFactory. + +Initiator methods provide same-process causal attribution only. Ambient presence is neither liveness proof nor authorization; subjects and owners remain explicit, as does identity at worker, process, persistence, and wire boundaries. Returned Promise boundaries drain during teardown, except a nested lineage that starts an owning-fiber unload is excluded from its own drain. + +```ts cordis-catalog +/** + * Read the Agent that initiated the inherited asynchronous driver chain. + * Use this optional form for logging, tracing, metrics, or host attribution + * that also supports agentless calls. When a parent creates a child, setup + * reports the causal parent while `agentCtx.agent` identifies the child. + * @returns the inherited Agent, or `undefined` outside an initiator boundary + * and inside an explicit clearing boundary. + * @throws when this service instance has been disposed. + */ +currentInitiator(): Agent | undefined + +/** + * Read the initiating Agent and fail when no initiator boundary is active. + * Use this for private helpers contractually below a driver, or for a + * deployment-owned outbound request whose contract forbids agentless calls. + * Generic or direct-call seams use optional lookup or explicit request fields. + * @returns the inherited Agent. + * @throws when no initiator is active or this service instance has been disposed. + */ +requireInitiator(): Agent + +/** + * Run an operation with one exact Agent as its process-local initiator. The + * exact synchronous value or Promise returned by the operation is preserved. + * Custom drivers and test harnesses wrap their complete returned foreground + * lifetime. + * A queue or wire receiver may establish this boundary only after validating + * explicit identity and resolving the exact live Agent; this method does neither. + * Detached work remains owned by the subsystem that starts it. + * @param agent - initiating Agent to inherit; presence is neither liveness proof nor authorization. + * @param operation - synchronous or asynchronous operation to invoke. + * @returns the exact value returned by `operation`. + * @throws when the initiator scope is closing/disposed, or when `operation` throws. + */ +withInitiator(agent: Agent, operation: () => T): T + +/** + * Run an operation inside a boundary that hides any inherited initiating + * Agent. The exact synchronous value or Promise is preserved. + * Use this while creating lazy shared timers, queue pumps, pool maintenance, + * watchers, or exporters so they do not inherit the first Agent that happens + * to initialize them. It clears only initiator attribution, not explicit + * fields, and does not own or drain detached resources. + * @param operation - synchronous or asynchronous operation to invoke without an initiator. + * @returns the exact value returned by `operation`. + * @throws when the initiator scope is closing/disposed, or when `operation` throws. + */ +withoutInitiator(operation: () => T): T + +/** + * Register the agent-creation factory (the loop calls this on construction, + * effect-scoped). A traced Cordis service is canonicalized to its concrete + * target; each create/resume call is then traced through that caller's + * context so ownership follows the caller without stacking proxy layers. + * Throws if a factory is already registered. Returns the disposer; on + * dispose the factory slot is cleared. + * @param factory - the loop-owned factory {@link create}/{@link resume} delegate to. + * @returns the disposer that clears the factory slot. The exact + * Cordis effect disposer (single-shot): composite (generator) effects may + * yield it directly — exact identity nests the teardown in order. + */ +setFactory(factory: AgentFactory): () => void + +/** + * Create and publish a new agent through the registered factory. + * Distinct from {@link register} (which records an already-constructed + * agent): this constructs the agent and its session. Rejects if no factory is + * registered or creation/setup fails. The resolved {@link AgentHandle} lets + * the owner tear down exactly this agent. + * @param options - shared identity, session seed/metadata, and agent options. + * @returns the handle after setup, rollback-covered publication, and loop start complete. + */ +async create(options: CreateAgentOptions): Promise + +/** + * Load a persisted session and resume an agent on it through the registered + * factory. Rejects if no factory is registered; the factory rejects if + * session persistence is not configured or persistence/setup fails. + * @param options - persisted identity, configuration, and optional setup. + * @returns the handle after setup, rollback-covered publication, and loop start complete. + */ +async resume(options: ResumeAgentOptions): Promise + +/** + * Register a live agent. Throws if an agent with the same id is already + * registered. Emits `agent/created` on registration and `agent/disposed` + * when the calling fiber is disposed — both with the agent's scope carrier + * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the + * emits are scope-filtered regardless of which context invoked `register` + * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always + * requires passing the carrier). Returns the disposer. + * @param agent - the already-constructed agent to record in the store. + * @returns the EXACT Cordis effect disposer (single-shot; a repeat call + * returns undefined without awaiting an in-flight teardown). Exact + * identity is load-bearing: a composite (generator) effect that owns a + * teardown ORDER — the agent factory's lifecycle chain — must yield THIS + * function so Cordis nests the unregistration at that yield position; + * yielding a wrapper would leave it disposing as a concurrent sibling on + * owner unload, unregistering the agent (and emitting `agent/disposed`) + * while its final turn is still draining. + */ +register(agent: Agent): () => void + +/** + * Insert an already-constructed agent without announcing it. This is the + * advanced ordered-lifecycle primitive used by the async agent factory: it + * first completes setup while the agent is unpublished, then assigns the + * returned detach closure into its pre-installed composite teardown before + * calling {@link announce}. Ordinary callers use {@link register}. + * @param agent - the prepared, unpublished agent. + * @param owner - live agent whose scoped context created this agent, or + * undefined for a top-level runtime root. This is runtime ownership, not + * the resumed session's durable parent lineage. + * @returns an idempotent closure that removes this exact entry and emits + * `agent/disposed` with listener failures contained. When called from a + * synchronous `agent/created` listener, removal and disposal wait until + * that creation dispatch unwinds. + */ +enter(agent: Agent, owner: Agent | undefined): () => void + +/** + * Announce an agent previously inserted with {@link enter}. + * @param agent - the live inserted agent to announce. + * @throws if `agent` is not the exact live registry entry for its id, or its + * creation announcement already began (including a reentrant call from a + * creation listener). + */ +announce(agent: Agent): void + +/** + * Look up a live agent. + * @param id - the shared agent/session id to look up. + * @returns the agent, or undefined when no live agent has that id. + */ +get(id: SessionId): Agent | undefined + +/** + * Test whether a live agent was created through one exact parent agent's + * scoped context. Runtime ownership is independent of durable session + * lineage and remains unambiguous when unrelated providers reuse an id. + * @param id - the candidate child agent's shared agent/session id. + * @param owner - the expected runtime creator agent. + * @returns true only while the exact child entry is live under that owner. + */ +isOwnedBy(id: SessionId, owner: Agent): boolean + +/** + * All live agents, in registration order. + * @returns a fresh array; mutating it does not affect the registry. + */ +list(): Agent[] + +/** + * All live top-level agents in registration order. A top-level agent was + * created without an owning agent context; durable session lineage does not + * affect this runtime relation, so a resumed fork may still be a root. + * @returns a fresh array; mutating it does not affect the registry. + */ +roots(): Agent[] +``` + +Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) + + + +### `agent/*` events + + + +#### `agent/created` — emit + +A fully configured agent and live session were published. Setup is composition-only; `agent/session-start` is the first startup-driving seam. Synchronous listener failure vetoes publication, while returned-promise rejection is reported. Detach requested during dispatch waits until every creation listener has observed the stable entry. + +```ts cordis-catalog +/** + * A fully configured agent and live session were published. Setup is + * composition-only; `agent/session-start` is the first startup-driving seam. + * Synchronous listener failure vetoes publication, while returned-promise + * rejection is reported. Detach requested during dispatch waits until every + * creation listener has observed the stable entry. + * @param payload.agent - the newly registered agent with its live session and completed setup. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/created'(this: Scoped, payload: { agent: Agent }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:158`](../../packages/core/agent/src/types.ts) + + + +#### `agent/disposed` — emit + +An agent left the registry; AgentLoop emits this after driver quiescence and scoped-registration unwind, but before session detachment. Custom registry users own their driver-ordering contract. + +```ts cordis-catalog +/** + * An agent left the registry; AgentLoop emits this after driver quiescence + * and scoped-registration unwind, but before session detachment. Custom + * registry users own their driver-ordering contract. + * @param payload.agent - the exact agent removed from the registry. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/types.ts) + + + +#### `agent/error` — emit + +A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. + +```ts cordis-catalog +/** + * A step or turn errored. The machine reports a failure here even when + * the error has no in-turn position for a durable record. + * @param payload.agent - the agent whose turn errored. + * @param payload.turn - the turn in which the failure surfaced. + * @param payload.step - the step at which the failure surfaced. + * @param payload.error - the failure, verbatim. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/claimed` — emit + +One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. + +```ts cordis-catalog +/** + * One message left the inbox inside its open turn. If the proposed step + * is rejected, the claimed message ends here: it is neither discarded nor + * re-emitted as a user/message, and the turn closes without a step. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the claimed message. + * @param payload.turn - the owning turn. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/discarded` — emit + +One message was discarded from the live inbox. + +```ts cordis-catalog +/** + * One message was discarded from the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the discarded message. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/inserted` — emit + +One message entered the live inbox. + +```ts cordis-catalog +/** + * One message entered the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the inserted message. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) + + + +#### `agent/pre-step` — waterfall + +Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. + +```ts cordis-catalog +/** + * Reject a proposed step or replace the messages that enter it. Calling + * `next()` preserves the current messages. + * @param payload.agent - the agent proposing the step. + * @param payload.messages - messages removed from the inbox for this step. + * @param payload.turn - the turn that will own the step. + * @param payload.step - the step proposed by the loop. + * @param payload.signal - the current turn's cancellation signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall + */ +'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:230`](../../packages/core/agent/src/types.ts) + + + +#### `agent/request` — waterfall + +Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. Model-visible content must use logged channels; this seam cannot mutate messages. + +```ts cordis-catalog +/** + * Replace the frozen call configuration. `await next()` yields the config + * the machine would use (agent options on the first request, the logged + * header afterwards); return a replacement to switch. Model-visible + * content must use logged channels; this seam cannot mutate messages. + * @param payload.agent - the agent making the model call. + * @param payload.turn - the open turn number. + * @param payload.step - the step whose request this is. + * @param payload.signal - the current turn's explicit abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall +*/ +'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) + + + +#### `agent/request-error` — waterfall + +Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. + +```ts cordis-catalog +/** + * Handle one failed model-request attempt before the loop retries or closes + * its step. A listener returns `{ kind: 'retry' }` without calling `next()` + * when it owns recovery, or calls `next()` to delegate. The default + * `undefined` leaves the failure terminal. + * @param payload.agent - the agent whose request failed. + * @param payload.turn - the turn containing the failed request. + * @param payload.step - the step containing the failed request attempt. + * @param payload.provider - the provider selected for the failed request. + * @param payload.failure - serializable facts normalized at the final adapter boundary. + * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. + * @param payload.signal - the turn abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall + */ +'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) + + + +#### `agent/session-start` — emit + +The session lifecycle began, once before the first turn. Use `agent.inject()` to seed model-facing context. This is a notification, not a veto; disposal requested by a lifecycle owner is rechecked before the driver starts. + +```ts cordis-catalog +/** + * The session lifecycle began, once before the first turn. Use + * `agent.inject()` to seed model-facing context. This is a notification, not + * a veto; disposal requested by a lifecycle owner is rechecked before the + * driver starts. + * @param payload.agent - the agent whose session lifecycle began. + * @param payload.source - why the session started (fresh startup, resume, …). + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/types.ts) + + + +#### `agent/status` — emit + +Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. + +```ts cordis-catalog +/** + * Agent status changed (`idle` ⇄ `running`). A waking delivery enters + * `running` synchronously after reserving cancellation; `idle` means no + * driver remains scheduled or active. + * @param payload.agent - the agent whose status flipped. + * @param payload.status - the status just entered (the transition's destination). + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/types.ts) + + + +#### `agent/turn-stopping` — serial + +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. + +```ts cordis-catalog +/** + * The turn is about to close: the model owes no response (no live tool + * calls, no fresh steering). Awaited before the boundary commits — a + * listener that objects steers (`agent.steer(...)`) and the machine + * re-reads its inbox: fresh steering runs another step, none closes the + * turn. Data decides, so listener order cannot change the outcome. The + * inverse control (stop a tool loop early) is data too: a tool result + * carrying `concludesTurn` ends the turn at its step. The conclusion + * never short-circuits already-submitted next-step work: same-step + * `additionalContexts` or racing steering still runs, and the turn + * closes only when that inbox drains. + * @param payload.agent - the agent whose turn is at its stop boundary. + * @param payload.turn - the turn about to close. + * @param payload.signal - the current turn's explicit abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode serial + */ +'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:277`](../../packages/core/agent/src/types.ts) + + + +### `agent-loop/*` events + + + +#### `agent-loop/config-start-failed` — emit + +A declarative agent entry failed before it could publish a live agent. Consumers that buffer work for the configured identity use this transient signal to reject that work instead of waiting forever. Normal factory teardown suppresses failures from the cancelled startup attempt. + +```ts cordis-catalog +/** + * A declarative agent entry failed before it could publish a live agent. + * Consumers that buffer work for the configured identity use this + * transient signal to reject that work instead of waiting forever. Normal + * factory teardown suppresses failures from the cancelled startup attempt. + * @param payload.sessionId - exact shared agent/session identity that failed startup. + * @param payload.error - persistence, setup, or publication failure. + * @mode emit + */ +'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +``` + +Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) + diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 0cd6a57971..ef5b70ae33 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -53,9 +53,10 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数 | [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | | [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | | [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | +| [session-projection.md](session-projection.md) | 投影 seam:`SessionProjectionMap`、纯函数 `ProjectionDefinition` 单元、`ProjectionSnapshot` 的一致切面、变更馈送 | | [telemetry.md](telemetry.md) | 对外上报 seam:`TelemetryRecord`/`TelemetrySeverity`、`TelemetryBackend` 契约、`telemetry/record` 脱敏 waterfall | -> 这些页面上的类型声明及其 JSDoc 与源码等价,并由 `pnpm run verify-type-equiv` 检查漂移(见 [development.md](../development.md#documenting-types-verbatim-ts-type-equiv))。普通块保留完整声明;`public-api` 块保留去除实现体的公开 class 声明。Cordis 服务使用生成的[服务目录](../cordis-catalog/services.md)。 +> 这些页面上的类型声明及其 JSDoc 与源码等价,并由 `pnpm run verify-type-equiv` 检查漂移(见 [development.md](../development.md#documenting-types-verbatim-ts-type-equiv))。普通块保留完整声明;`public-api` 块保留去除实现体的公开 class 声明。Cordis 服务使用生成的[服务目录](#cordis-surface)。 @@ -788,3 +789,546 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact' 唯一属于核心的流水线编写类型:每个已注册工具*是什么*——一个面向模型的 `ToolSchema` 加上一个 `execute` 函数,以及可选的最终内容回调与 UI 回调。工具作者很少手动构造它(`defineTool` DSL 会用类型化参数构建),但它是注册表持有、循环分发所经过的契约。 其完整字段、`defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` 类型化 schema DSL、`ToolExecution`/`ToolExecutionResult` waterfall 形状,以及工具展示 UI 词汇在 **[tools.md](tools.md)** 中。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.agentLoop` — `AgentLoop` + +Concrete agent factory and driver service. + +```ts cordis-catalog +/** + * Create an agent and session under one caller-supplied identity, owned by + * the accessing fiber. Constructor-driven config calls mint a fresh combined + * id before entering this boundary. + * @param id - shared agent/session identity. + * @param options - concrete loop options. + * @param meta - optional fresh-session workspace metadata. + * @returns the published running agent. + */ +create(id: SessionId, options: AgentOptions = {}, meta: Pick = {}): Agent + +/** + * Create an owned agent on a caller-supplied session id. + * @param ownerCtx - caller context that structurally owns the lifecycle. + * @param options - identities, session seed/metadata, loop options, setup, and cancellation. + * @returns the published handle. + */ +async createAgent(ownerCtx: Context, options: CreateAgentOptions): Promise + +/** + * Resume an owned agent from the configured persistence service. + * @param ownerCtx - caller context that owns load, setup, and the live lifecycle. + * @param options - persisted identity, loop options, setup, and cancellation. + * @returns the published handle. + */ +async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise +``` + +Types: [SessionHeader](persistence.md) + +Source: [`packages/core/agent-loop/src/index.ts:277`](../../packages/core/agent-loop/src/index.ts) + + + +### `ctx.agents` — `AgentRegistry` + +Agent service (`ctx.agents`): tracks live agents and carries the initiating Agent through one process-local asynchronous driver chain. Agent *creation* is provided by whichever plugin implements the AgentFactory (`@deepseek-ai/dsh-agent-loop`), registered via setFactory. + +Initiator methods provide same-process causal attribution only. Ambient presence is neither liveness proof nor authorization; subjects and owners remain explicit, as does identity at worker, process, persistence, and wire boundaries. Returned Promise boundaries drain during teardown, except a nested lineage that starts an owning-fiber unload is excluded from its own drain. + +```ts cordis-catalog +/** + * Read the Agent that initiated the inherited asynchronous driver chain. + * Use this optional form for logging, tracing, metrics, or host attribution + * that also supports agentless calls. When a parent creates a child, setup + * reports the causal parent while `agentCtx.agent` identifies the child. + * @returns the inherited Agent, or `undefined` outside an initiator boundary + * and inside an explicit clearing boundary. + * @throws when this service instance has been disposed. + */ +currentInitiator(): Agent | undefined + +/** + * Read the initiating Agent and fail when no initiator boundary is active. + * Use this for private helpers contractually below a driver, or for a + * deployment-owned outbound request whose contract forbids agentless calls. + * Generic or direct-call seams use optional lookup or explicit request fields. + * @returns the inherited Agent. + * @throws when no initiator is active or this service instance has been disposed. + */ +requireInitiator(): Agent + +/** + * Run an operation with one exact Agent as its process-local initiator. The + * exact synchronous value or Promise returned by the operation is preserved. + * Custom drivers and test harnesses wrap their complete returned foreground + * lifetime. + * A queue or wire receiver may establish this boundary only after validating + * explicit identity and resolving the exact live Agent; this method does neither. + * Detached work remains owned by the subsystem that starts it. + * @param agent - initiating Agent to inherit; presence is neither liveness proof nor authorization. + * @param operation - synchronous or asynchronous operation to invoke. + * @returns the exact value returned by `operation`. + * @throws when the initiator scope is closing/disposed, or when `operation` throws. + */ +withInitiator(agent: Agent, operation: () => T): T + +/** + * Run an operation inside a boundary that hides any inherited initiating + * Agent. The exact synchronous value or Promise is preserved. + * Use this while creating lazy shared timers, queue pumps, pool maintenance, + * watchers, or exporters so they do not inherit the first Agent that happens + * to initialize them. It clears only initiator attribution, not explicit + * fields, and does not own or drain detached resources. + * @param operation - synchronous or asynchronous operation to invoke without an initiator. + * @returns the exact value returned by `operation`. + * @throws when the initiator scope is closing/disposed, or when `operation` throws. + */ +withoutInitiator(operation: () => T): T + +/** + * Register the agent-creation factory (the loop calls this on construction, + * effect-scoped). A traced Cordis service is canonicalized to its concrete + * target; each create/resume call is then traced through that caller's + * context so ownership follows the caller without stacking proxy layers. + * Throws if a factory is already registered. Returns the disposer; on + * dispose the factory slot is cleared. + * @param factory - the loop-owned factory {@link create}/{@link resume} delegate to. + * @returns the disposer that clears the factory slot. The exact + * Cordis effect disposer (single-shot): composite (generator) effects may + * yield it directly — exact identity nests the teardown in order. + */ +setFactory(factory: AgentFactory): () => void + +/** + * Create and publish a new agent through the registered factory. + * Distinct from {@link register} (which records an already-constructed + * agent): this constructs the agent and its session. Rejects if no factory is + * registered or creation/setup fails. The resolved {@link AgentHandle} lets + * the owner tear down exactly this agent. + * @param options - shared identity, session seed/metadata, and agent options. + * @returns the handle after setup, rollback-covered publication, and loop start complete. + */ +async create(options: CreateAgentOptions): Promise + +/** + * Load a persisted session and resume an agent on it through the registered + * factory. Rejects if no factory is registered; the factory rejects if + * session persistence is not configured or persistence/setup fails. + * @param options - persisted identity, configuration, and optional setup. + * @returns the handle after setup, rollback-covered publication, and loop start complete. + */ +async resume(options: ResumeAgentOptions): Promise + +/** + * Register a live agent. Throws if an agent with the same id is already + * registered. Emits `agent/created` on registration and `agent/disposed` + * when the calling fiber is disposed — both with the agent's scope carrier + * (`scopeTarget(agent, agent)`): the subject is the agent in hand, so the + * emits are scope-filtered regardless of which context invoked `register` + * (calling through `agent.ctx` scopes EFFECTS; dispatch scoping always + * requires passing the carrier). Returns the disposer. + * @param agent - the already-constructed agent to record in the store. + * @returns the EXACT Cordis effect disposer (single-shot; a repeat call + * returns undefined without awaiting an in-flight teardown). Exact + * identity is load-bearing: a composite (generator) effect that owns a + * teardown ORDER — the agent factory's lifecycle chain — must yield THIS + * function so Cordis nests the unregistration at that yield position; + * yielding a wrapper would leave it disposing as a concurrent sibling on + * owner unload, unregistering the agent (and emitting `agent/disposed`) + * while its final turn is still draining. + */ +register(agent: Agent): () => void + +/** + * Insert an already-constructed agent without announcing it. This is the + * advanced ordered-lifecycle primitive used by the async agent factory: it + * first completes setup while the agent is unpublished, then assigns the + * returned detach closure into its pre-installed composite teardown before + * calling {@link announce}. Ordinary callers use {@link register}. + * @param agent - the prepared, unpublished agent. + * @param owner - live agent whose scoped context created this agent, or + * undefined for a top-level runtime root. This is runtime ownership, not + * the resumed session's durable parent lineage. + * @returns an idempotent closure that removes this exact entry and emits + * `agent/disposed` with listener failures contained. When called from a + * synchronous `agent/created` listener, removal and disposal wait until + * that creation dispatch unwinds. + */ +enter(agent: Agent, owner: Agent | undefined): () => void + +/** + * Announce an agent previously inserted with {@link enter}. + * @param agent - the live inserted agent to announce. + * @throws if `agent` is not the exact live registry entry for its id, or its + * creation announcement already began (including a reentrant call from a + * creation listener). + */ +announce(agent: Agent): void + +/** + * Look up a live agent. + * @param id - the shared agent/session id to look up. + * @returns the agent, or undefined when no live agent has that id. + */ +get(id: SessionId): Agent | undefined + +/** + * Test whether a live agent was created through one exact parent agent's + * scoped context. Runtime ownership is independent of durable session + * lineage and remains unambiguous when unrelated providers reuse an id. + * @param id - the candidate child agent's shared agent/session id. + * @param owner - the expected runtime creator agent. + * @returns true only while the exact child entry is live under that owner. + */ +isOwnedBy(id: SessionId, owner: Agent): boolean + +/** + * All live agents, in registration order. + * @returns a fresh array; mutating it does not affect the registry. + */ +list(): Agent[] + +/** + * All live top-level agents in registration order. A top-level agent was + * created without an owning agent context; durable session lineage does not + * affect this runtime relation, so a resumed fork may still be a root. + * @returns a fresh array; mutating it does not affect the registry. + */ +roots(): Agent[] +``` + +Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) + + + +### `agent/*` events + + + +#### `agent/created` — emit + +A fully configured agent and live session were published. Setup is composition-only; `agent/session-start` is the first startup-driving seam. Synchronous listener failure vetoes publication, while returned-promise rejection is reported. Detach requested during dispatch waits until every creation listener has observed the stable entry. + +```ts cordis-catalog +/** + * A fully configured agent and live session were published. Setup is + * composition-only; `agent/session-start` is the first startup-driving seam. + * Synchronous listener failure vetoes publication, while returned-promise + * rejection is reported. Detach requested during dispatch waits until every + * creation listener has observed the stable entry. + * @param payload.agent - the newly registered agent with its live session and completed setup. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/created'(this: Scoped, payload: { agent: Agent }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:158`](../../packages/core/agent/src/types.ts) + + + +#### `agent/disposed` — emit + +An agent left the registry; AgentLoop emits this after driver quiescence and scoped-registration unwind, but before session detachment. Custom registry users own their driver-ordering contract. + +```ts cordis-catalog +/** + * An agent left the registry; AgentLoop emits this after driver quiescence + * and scoped-registration unwind, but before session detachment. Custom + * registry users own their driver-ordering contract. + * @param payload.agent - the exact agent removed from the registry. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/types.ts) + + + +#### `agent/error` — emit + +A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. + +```ts cordis-catalog +/** + * A step or turn errored. The machine reports a failure here even when + * the error has no in-turn position for a durable record. + * @param payload.agent - the agent whose turn errored. + * @param payload.turn - the turn in which the failure surfaced. + * @param payload.step - the step at which the failure surfaced. + * @param payload.error - the failure, verbatim. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/claimed` — emit + +One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. + +```ts cordis-catalog +/** + * One message left the inbox inside its open turn. If the proposed step + * is rejected, the claimed message ends here: it is neither discarded nor + * re-emitted as a user/message, and the turn closes without a step. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the claimed message. + * @param payload.turn - the owning turn. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/discarded` — emit + +One message was discarded from the live inbox. + +```ts cordis-catalog +/** + * One message was discarded from the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the discarded message. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) + + + +#### `agent/inbox/inserted` — emit + +One message entered the live inbox. + +```ts cordis-catalog +/** + * One message entered the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the inserted message. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) + + + +#### `agent/pre-step` — waterfall + +Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. + +```ts cordis-catalog +/** + * Reject a proposed step or replace the messages that enter it. Calling + * `next()` preserves the current messages. + * @param payload.agent - the agent proposing the step. + * @param payload.messages - messages removed from the inbox for this step. + * @param payload.turn - the turn that will own the step. + * @param payload.step - the step proposed by the loop. + * @param payload.signal - the current turn's cancellation signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall + */ +'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) · [UserMessage](session.md) + +Source: [`packages/core/agent/src/types.ts:230`](../../packages/core/agent/src/types.ts) + + + +#### `agent/request` — waterfall + +Replace the frozen call configuration. `await next()` yields the config the machine would use (agent options on the first request, the logged header afterwards); return a replacement to switch. Model-visible content must use logged channels; this seam cannot mutate messages. + +```ts cordis-catalog +/** + * Replace the frozen call configuration. `await next()` yields the config + * the machine would use (agent options on the first request, the logged + * header afterwards); return a replacement to switch. Model-visible + * content must use logged channels; this seam cannot mutate messages. + * @param payload.agent - the agent making the model call. + * @param payload.turn - the open turn number. + * @param payload.step - the step whose request this is. + * @param payload.signal - the current turn's explicit abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall +*/ +'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) + + + +#### `agent/request-error` — waterfall + +Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. + +```ts cordis-catalog +/** + * Handle one failed model-request attempt before the loop retries or closes + * its step. A listener returns `{ kind: 'retry' }` without calling `next()` + * when it owns recovery, or calls `next()` to delegate. The default + * `undefined` leaves the failure terminal. + * @param payload.agent - the agent whose request failed. + * @param payload.turn - the turn containing the failed request. + * @param payload.step - the step containing the failed request attempt. + * @param payload.provider - the provider selected for the failed request. + * @param payload.failure - serializable facts normalized at the final adapter boundary. + * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. + * @param payload.signal - the turn abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode waterfall + */ +'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +``` + +Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:259`](../../packages/core/agent/src/types.ts) + + + +#### `agent/session-start` — emit + +The session lifecycle began, once before the first turn. Use `agent.inject()` to seed model-facing context. This is a notification, not a veto; disposal requested by a lifecycle owner is rechecked before the driver starts. + +```ts cordis-catalog +/** + * The session lifecycle began, once before the first turn. Use + * `agent.inject()` to seed model-facing context. This is a notification, not + * a veto; disposal requested by a lifecycle owner is rechecked before the + * driver starts. + * @param payload.agent - the agent whose session lifecycle began. + * @param payload.source - why the session started (fresh startup, resume, …). + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/types.ts) + + + +#### `agent/status` — emit + +Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. + +```ts cordis-catalog +/** + * Agent status changed (`idle` ⇄ `running`). A waking delivery enters + * `running` synchronously after reserving cancellation; `idle` means no + * driver remains scheduled or active. + * @param payload.agent - the agent whose status flipped. + * @param payload.status - the status just entered (the transition's destination). + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/types.ts) + + + +#### `agent/turn-stopping` — serial + +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. + +```ts cordis-catalog +/** + * The turn is about to close: the model owes no response (no live tool + * calls, no fresh steering). Awaited before the boundary commits — a + * listener that objects steers (`agent.steer(...)`) and the machine + * re-reads its inbox: fresh steering runs another step, none closes the + * turn. Data decides, so listener order cannot change the outcome. The + * inverse control (stop a tool loop early) is data too: a tool result + * carrying `concludesTurn` ends the turn at its step. The conclusion + * never short-circuits already-submitted next-step work: same-step + * `additionalContexts` or racing steering still runs, and the turn + * closes only when that inbox drains. + * @param payload.agent - the agent whose turn is at its stop boundary. + * @param payload.turn - the turn about to close. + * @param payload.signal - the current turn's explicit abort signal. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode serial + */ +'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:277`](../../packages/core/agent/src/types.ts) + + + +### `agent-loop/*` events + + + +#### `agent-loop/config-start-failed` — emit + +A declarative agent entry failed before it could publish a live agent. Consumers that buffer work for the configured identity use this transient signal to reject that work instead of waiting forever. Normal factory teardown suppresses failures from the cancelled startup attempt. + +```ts cordis-catalog +/** + * A declarative agent entry failed before it could publish a live agent. + * Consumers that buffer work for the configured identity use this + * transient signal to reject that work instead of waiting forever. Normal + * factory teardown suppresses failures from the cancelled startup attempt. + * @param payload.sessionId - exact shared agent/session identity that failed startup. + * @param payload.error - persistence, setup, or publication failure. + * @mode emit + */ +'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +``` + +Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) + diff --git a/docs/subsystems/credentials.i18n.yaml b/docs/subsystems/credentials.i18n.yaml index f7899eaf6e..7028d71f8d 100644 --- a/docs/subsystems/credentials.i18n.yaml +++ b/docs/subsystems/credentials.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/credentials.md -credentials.md: ef74ddeb4346e18f8d5d33488657e5d50f1d754e -credentials.zh.md: 09cf374a2346fd93aa834e3372321e6eeece6ed8 +credentials.md: 0bc2224ac039addc795d3806e8c85004f2bb84a7 +credentials.zh.md: 6f5ccd1572885434257d39eb0f659fcc7371ad1d diff --git a/docs/subsystems/credentials.md b/docs/subsystems/credentials.md index ef74ddeb43..0bc2224ac0 100644 --- a/docs/subsystems/credentials.md +++ b/docs/subsystems/credentials.md @@ -48,3 +48,86 @@ interface CredentialInfo { ## Change commits `credentials/updated (ref)` fires after a committed change to a provider-managed source — a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Consumers do not need the event (they re-resolve per operation); it exists for configuration surfaces refreshing a "configured" badge. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.credentials` — `Credentials` (abstract seam) + +Abstract credential service. Providers implement the four operations over their source layers; one seam-wide rule binds them all: an empty stored value is absent everywhere — `resolve` skips it, `describe` reports it unconfigured — so a blank never masquerades as a configured secret. + +```ts cordis-catalog +/** + * Resolve one reference to its current value. Resolution is per call: + * consumers re-resolve at each operation and must not cache across + * operations — that per-operation read is what makes a changed credential + * reach the next operation without a restart. + * @param ref - the reference to resolve. + * @returns the value and its source, or `undefined` while unconfigured. + */ +abstract resolve(ref: CredentialRef): Promise + +/** + * Describe one reference for configuration surfaces without exposing the + * value. + * @param ref - the reference to describe. + * @returns configured state, supplying source, and writability. + */ +abstract describe(ref: CredentialRef): Promise + +/** + * Durably store one value in the provider-managed writable source. Rejects + * while a read-only source shadows the reference — the write would appear + * to succeed while resolution keeps returning the shadowing value — and + * rejects an empty value (use {@link unset}). + * @param ref - the reference to store. + * @param value - the non-empty secret value. + */ +abstract set(ref: CredentialRef, value: string): Promise + +/** + * Remove one reference from the provider-managed writable source; removing + * an absent reference is a no-op. Rejects while a read-only source shadows + * the reference, like {@link set}. + * @param ref - the reference to remove. + */ +abstract unset(ref: CredentialRef): Promise +``` + +Source: [`packages/credentials/credentials/src/index.ts:77`](../../packages/credentials/credentials/src/index.ts) + + + +### `credentials/*` events + + + +#### `credentials/updated` — emit + +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. + +```ts cordis-catalog +/** + * Committed change to a provider-managed credential source: a `set`, an + * `unset`, or an external edit observed in storage. Ambient + * process-environment changes are not observable and never emit. Listener + * failures are contained and logged — a sync throw and an async rejection + * alike — without changing the committed operation's outcome, except + * `INVARIANT`-coded failures, which rethrow after every listener ran; + * that rethrow reaches the emitter only from synchronous listeners, so + * invariant checks on this event must not be async functions. + * @param ref - the reference whose stored value changed. + * @mode emit + */ +'credentials/updated'(ref: CredentialRef): void +``` + +Source: [`packages/credentials/credentials/src/index.ts:67`](../../packages/credentials/credentials/src/index.ts) + diff --git a/docs/subsystems/credentials.zh.md b/docs/subsystems/credentials.zh.md index 09cf374a23..6f5ccd1572 100644 --- a/docs/subsystems/credentials.zh.md +++ b/docs/subsystems/credentials.zh.md @@ -48,3 +48,86 @@ interface CredentialInfo { ## 变更提交 `credentials/updated (ref)` 在 provider 管理的来源发生已提交变更后触发——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.credentials` — `Credentials` (abstract seam) + +Abstract credential service. Providers implement the four operations over their source layers; one seam-wide rule binds them all: an empty stored value is absent everywhere — `resolve` skips it, `describe` reports it unconfigured — so a blank never masquerades as a configured secret. + +```ts cordis-catalog +/** + * Resolve one reference to its current value. Resolution is per call: + * consumers re-resolve at each operation and must not cache across + * operations — that per-operation read is what makes a changed credential + * reach the next operation without a restart. + * @param ref - the reference to resolve. + * @returns the value and its source, or `undefined` while unconfigured. + */ +abstract resolve(ref: CredentialRef): Promise + +/** + * Describe one reference for configuration surfaces without exposing the + * value. + * @param ref - the reference to describe. + * @returns configured state, supplying source, and writability. + */ +abstract describe(ref: CredentialRef): Promise + +/** + * Durably store one value in the provider-managed writable source. Rejects + * while a read-only source shadows the reference — the write would appear + * to succeed while resolution keeps returning the shadowing value — and + * rejects an empty value (use {@link unset}). + * @param ref - the reference to store. + * @param value - the non-empty secret value. + */ +abstract set(ref: CredentialRef, value: string): Promise + +/** + * Remove one reference from the provider-managed writable source; removing + * an absent reference is a no-op. Rejects while a read-only source shadows + * the reference, like {@link set}. + * @param ref - the reference to remove. + */ +abstract unset(ref: CredentialRef): Promise +``` + +Source: [`packages/credentials/credentials/src/index.ts:77`](../../packages/credentials/credentials/src/index.ts) + + + +### `credentials/*` events + + + +#### `credentials/updated` — emit + +Committed change to a provider-managed credential source: a `set`, an `unset`, or an external edit observed in storage. Ambient process-environment changes are not observable and never emit. Listener failures are contained and logged — a sync throw and an async rejection alike — without changing the committed operation's outcome, except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. + +```ts cordis-catalog +/** + * Committed change to a provider-managed credential source: a `set`, an + * `unset`, or an external edit observed in storage. Ambient + * process-environment changes are not observable and never emit. Listener + * failures are contained and logged — a sync throw and an async rejection + * alike — without changing the committed operation's outcome, except + * `INVARIANT`-coded failures, which rethrow after every listener ran; + * that rethrow reaches the emitter only from synchronous listeners, so + * invariant checks on this event must not be async functions. + * @param ref - the reference whose stored value changed. + * @mode emit + */ +'credentials/updated'(ref: CredentialRef): void +``` + +Source: [`packages/credentials/credentials/src/index.ts:67`](../../packages/credentials/credentials/src/index.ts) + diff --git a/docs/subsystems/filesystem.i18n.yaml b/docs/subsystems/filesystem.i18n.yaml index 2f9478a7ba..cadc2b92d8 100644 --- a/docs/subsystems/filesystem.i18n.yaml +++ b/docs/subsystems/filesystem.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/filesystem.md -filesystem.md: addded9f673ed435e95109d4fb772967514c0b87 -filesystem.zh.md: 1e378928ed570b97dbec73b836a7e6ff18726ff8 +# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md +filesystem.md: 1999399254fa5d72ab222a42a2fa8f4b890da50a +filesystem.zh.md: 044a1c5be91ed8bec5d0442cc6f7edfde6870785 diff --git a/docs/subsystems/filesystem.md b/docs/subsystems/filesystem.md index addded9f67..1999399254 100644 --- a/docs/subsystems/filesystem.md +++ b/docs/subsystems/filesystem.md @@ -181,7 +181,7 @@ interface FsEditOutcome { `dsh-fs` owns three events the tool dispatches and the policy plugin listens for, so the emitter (`dsh-tool-fs`) and the listener (`dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. -`fs/write-intent` and `fs/edit-intent` are **single-slot decision waterfalls**: the tool dispatches each with a default thunk returning `undefined` (the bare provider), and a listener fully decides without calling `next()`. The slot is first-wins by registration order — the policy plugin owning it is a deployment convention, not an enforced invariant. `fs/observed` is a fire-and-forget recording event dispatched with a plain `ctx.emit`; its listener MUST be synchronous and side-effect-only, because the tool does NOT guard the emit — a throwing listener would surface as the tool's `isError` result for a mutation that already succeeded. The generated catalog shows the exact signatures on [events.md](../cordis-catalog/events.md). +`fs/write-intent` and `fs/edit-intent` are **single-slot decision waterfalls**: the tool dispatches each with a default thunk returning `undefined` (the bare provider), and a listener fully decides without calling `next()`. The slot is first-wins by registration order — the policy plugin owning it is a deployment convention, not an enforced invariant. `fs/observed` is a fire-and-forget recording event dispatched with a plain `ctx.emit`; its listener MUST be synchronous and side-effect-only, because the tool does NOT guard the emit — a throwing listener would surface as the tool's `isError` result for a mutation that already succeeded. The generated [cordis surface](#cordis-surface) below shows the exact signatures. ## Execution context (policy plugin) @@ -258,4 +258,208 @@ type FsErrorCode = ## The service and the plugin -`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated wiring catalog shows the exact `ctx.fs` signatures on [services.md](../cordis-catalog/services.md#ctxfs--filesystem-abstract-seam). +`FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.fs` — `FileSystem` (abstract seam) + +Abstract filesystem provider. Targets must preserve identity across aliases; reads expose regular UTF-8 text or typed errors, listings are stable and content-free, and mutations are atomic. Optional guards add stale protection without changing the unguarded provider contract. + +```ts cordis-catalog +/** + * Resolve a model/plugin-supplied path into a stable {@link FsTarget}. May perform I/O (a + * remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence + * async even though the local backend only normalizes + realpaths. + * + * @param path - the path to resolve; relative paths resolve against `opts.cwd`. + * @param opts - optional cwd override and cancellation signal. + * @returns the stable target; the same file yields the same `targetKey`. + */ +abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise + +/** + * Return the canonical absolute path a subprocess in this filesystem's + * execution world can open. The path is deliberately separate from + * {@link FsTarget.targetKey}: consumers may pass this value to another OS + * capability, but must continue treating the target key as opaque. + * @param target - the resolved target whose process path is required. + * @returns an absolute path in the backend's execution world. + */ +abstract processPath(target: FsTarget): string + +/** + * Return the canonical `file:` URI for a target in this filesystem's + * execution world. Backends own URI encoding because the host platform may + * differ from the execution platform. + * @param target - the resolved target to encode. + * @returns the target's canonical file URI. + */ +abstract fileUrl(target: FsTarget): string + +/** + * Test canonical containment without exposing or parsing backend target + * keys. Both targets must come from this provider. + * @param parent - canonical directory target. + * @param child - canonical candidate target. + * @returns true when `child` is `parent` or a descendant of it. + */ +abstract contains(parent: FsTarget, child: FsTarget): boolean + +/** + * Return target metadata, or `undefined` when the target does not exist. + * @param target - the resolved target to stat. + * @param signal - aborts the metadata round-trip. + * @returns metadata only, never content; undefined for an absent target. + */ +abstract stat(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Return path metadata without following the final path component when it is a + * symbolic link. This is intentionally path-shaped, not target-shaped: + * {@link resolve} follows symlinks to produce the stable identity used by + * normal reads/writes, while `lstat` lets a consumer reject the path itself + * before that follow happens. + * + * `opts.cwd` follows {@link resolve}'s cwd rules. `undefined` means the path is + * absent. + * @param path - the path to inspect; relative paths resolve against `opts.cwd`. + * @param opts - `cwd` overrides the backend's default base for relative paths. + * @param signal - aborts the metadata round-trip. + * @returns metadata only, never content; undefined for an absent path. + */ +abstract lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise + +/** + * Read the whole regular text file as a single decoded string. + * @param target - the resolved target to read. + * @param signal - aborts the read. + * @returns the full decoded UTF-8 content. + */ +abstract readText(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Stream the whole regular text file as decoded text chunks (same text + * semantics as {@link readText}, for large files). The backend owns + * cross-chunk UTF-8 decoding and binary rejection so the policy layer never + * touches raw bytes. + * @param target - the resolved target to read. + * @param signal - aborts the stream, including between chunks. + * @returns the chunk iterable, decoded and validated like {@link readText}. + */ +abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> + +/** + * List direct children of a directory in stable name order. Returns resolved + * child targets plus cheap metadata only; never reads file contents. + * @param target - the resolved directory target. + * @param signal - aborts the listing. + * @returns one entry per direct child, in stable name order. + */ +abstract listDir(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Atomically create or replace UTF-8 text. `expected` guards intent and + * staleness; omission allows unconditional overwrite. + * @param target - the resolved target to write. + * @param content - the full new file content. + * @param expected - the write intent guarding the write; omit for unconditional. + * @param signal - aborts before the atomic rename takes effect. + * @param sandboxPolicy - the per-call mode and workspace root this write + * runs under; a sandboxing backend fences the write by it, the bare backend + * ignores it. Omit to leave the backend its own default. + * @returns the outcome, including the version the write produced. + */ +abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise + +/** + * Atomically edit literal text. When supplied, the version guard is checked + * before matching so stale content reports `FS_STALE_VERSION`; omission edits + * the current content without a freshness precondition. + * @param target - the resolved target to edit. + * @param edit - the literal search/replace request. + * @param expected - the version guard; omit for an unconditional edit. + * @param signal - aborts before the atomic rename takes effect. + * @param sandboxPolicy - the per-call mode and workspace root this edit runs + * under; a sandboxing backend fences the edit by it, the bare backend + * ignores it. Omit to leave the backend its own default. + * @returns the outcome, including the version the edit produced. + */ +abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise +``` + +Types: [SandboxExecutionPolicy](sandbox.md) + +Source: [`packages/fs/fs/src/index.ts:83`](../../packages/fs/fs/src/index.ts) + + + +### `fs/*` events + + + +#### `fs/edit-intent` — waterfall + +Single-slot decision for the next FileSystem.editText. Calling `next()` yields an unconditional edit; the first returned guard wins. + +```ts cordis-catalog +/** + * Single-slot decision for the next {@link FileSystem.editText}. Calling + * `next()` yields an unconditional edit; the first returned guard wins. + * @param target - the resolved target about to be edited. + * @param actor - the opaque tool-execution context the decider keys off. + * @mode waterfall + */ +'fs/edit-intent'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined> +``` + +Source: [`packages/fs/fs/src/index.ts:64`](../../packages/fs/fs/src/index.ts) + + + +#### `fs/observed` — emit + +Record a successful observation. Listeners must be synchronous recorders: throws fail the tool call and returned promises are not awaited. + +```ts cordis-catalog +/** + * Record a successful observation. Listeners must be synchronous recorders: + * throws fail the tool call and returned promises are not awaited. + * @param target - the target that was read/written/edited. + * @param version - the version the actor now holds as its observation. + * @param actor - the observing tool-execution context; undefined records nothing useful. + * @mode emit + */ +'fs/observed'(target: FsTarget, version: FsVersion, actor: object | undefined): void +``` + +Source: [`packages/fs/fs/src/index.ts:73`](../../packages/fs/fs/src/index.ts) + + + +#### `fs/write-intent` — waterfall + +Single-slot decision for the next FileSystem.writeText. Calling `next()` yields the bare provider's unconditional write; the first listener that returns an intent owns the decision rather than composing with peers. + +```ts cordis-catalog +/** + * Single-slot decision for the next {@link FileSystem.writeText}. Calling + * `next()` yields the bare provider's unconditional write; the first listener + * that returns an intent owns the decision rather than composing with peers. + * @param target - the resolved target about to be written. + * @param actor - the opaque tool-execution context the decider keys off. + * @mode waterfall + */ +'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise +``` + +Source: [`packages/fs/fs/src/index.ts:56`](../../packages/fs/fs/src/index.ts) + diff --git a/docs/subsystems/filesystem.zh.md b/docs/subsystems/filesystem.zh.md index 1e378928ed..044a1c5be9 100644 --- a/docs/subsystems/filesystem.zh.md +++ b/docs/subsystems/filesystem.zh.md @@ -181,7 +181,7 @@ interface FsEditOutcome { `dsh-fs` 拥有三个事件,由工具分发、策略插件监听,使发射方(`dsh-tool-fs`)与监听方(`dsh-fs-policy`)共享词汇,而发射方无需依赖策略插件。它们只携带 `dsh-fs` 词汇加一个不透明的 `object` actor,不含面向模型的概念,也不含 agent/会话所有者结构。 -`fs/write-intent` 与 `fs/edit-intent` 是**单槽决策 waterfall**:工具分发时附带一个默认 thunk(返回 `undefined`,即裸提供方),监听方完全决策而不调用 `next()`。该槽按注册顺序先到先得——由策略插件占据是部署约定,而非强制不变式。`fs/observed` 是一个即发即弃的记录事件,通过普通 `ctx.emit` 分发;其监听方必须是同步的、仅产生副作用,因为工具不会捕获该 emit 抛出的异常——抛出异常的监听方会导致工具为一次已经成功的变更返回 `isError` 结果。生成的目录在 [events.md](../cordis-catalog/events.md) 中展示确切签名。 +`fs/write-intent` 与 `fs/edit-intent` 是**单槽决策 waterfall**:工具分发时附带一个默认 thunk(返回 `undefined`,即裸提供方),监听方完全决策而不调用 `next()`。该槽按注册顺序先到先得——由策略插件占据是部署约定,而非强制不变式。`fs/observed` 是一个即发即弃的记录事件,通过普通 `ctx.emit` 分发;其监听方必须是同步的、仅产生副作用,因为工具不会捕获该 emit 抛出的异常——抛出异常的监听方会导致工具为一次已经成功的变更返回 `isError` 结果。下方生成的 [cordis surface](#cordis-surface) 展示确切签名。 ## 执行上下文(策略插件) @@ -258,4 +258,208 @@ type FsErrorCode = ## 服务与插件 -`FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:对写入/编辑意图 waterfall 作出决策(提供 `createIfAbsent`/`replaceIfVersion`/`{ version }`,或抛出 `FS_NOT_OBSERVED`),并在 `fs/observed` 上记录。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。生成的 wiring 目录在 [services.md](../cordis-catalog/services.md#ctxfs--filesystem-abstract-seam) 中展示确切的 `ctx.fs` 签名。 +`FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:对写入/编辑意图 waterfall 作出决策(提供 `createIfAbsent`/`replaceIfVersion`/`{ version }`,或抛出 `FS_NOT_OBSERVED`),并在 `fs/observed` 上记录。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.fs` — `FileSystem` (abstract seam) + +Abstract filesystem provider. Targets must preserve identity across aliases; reads expose regular UTF-8 text or typed errors, listings are stable and content-free, and mutations are atomic. Optional guards add stale protection without changing the unguarded provider contract. + +```ts cordis-catalog +/** + * Resolve a model/plugin-supplied path into a stable {@link FsTarget}. May perform I/O (a + * remote/sandboxed backend may need a round-trip to map a path to a stable identity), hence + * async even though the local backend only normalizes + realpaths. + * + * @param path - the path to resolve; relative paths resolve against `opts.cwd`. + * @param opts - optional cwd override and cancellation signal. + * @returns the stable target; the same file yields the same `targetKey`. + */ +abstract resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise + +/** + * Return the canonical absolute path a subprocess in this filesystem's + * execution world can open. The path is deliberately separate from + * {@link FsTarget.targetKey}: consumers may pass this value to another OS + * capability, but must continue treating the target key as opaque. + * @param target - the resolved target whose process path is required. + * @returns an absolute path in the backend's execution world. + */ +abstract processPath(target: FsTarget): string + +/** + * Return the canonical `file:` URI for a target in this filesystem's + * execution world. Backends own URI encoding because the host platform may + * differ from the execution platform. + * @param target - the resolved target to encode. + * @returns the target's canonical file URI. + */ +abstract fileUrl(target: FsTarget): string + +/** + * Test canonical containment without exposing or parsing backend target + * keys. Both targets must come from this provider. + * @param parent - canonical directory target. + * @param child - canonical candidate target. + * @returns true when `child` is `parent` or a descendant of it. + */ +abstract contains(parent: FsTarget, child: FsTarget): boolean + +/** + * Return target metadata, or `undefined` when the target does not exist. + * @param target - the resolved target to stat. + * @param signal - aborts the metadata round-trip. + * @returns metadata only, never content; undefined for an absent target. + */ +abstract stat(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Return path metadata without following the final path component when it is a + * symbolic link. This is intentionally path-shaped, not target-shaped: + * {@link resolve} follows symlinks to produce the stable identity used by + * normal reads/writes, while `lstat` lets a consumer reject the path itself + * before that follow happens. + * + * `opts.cwd` follows {@link resolve}'s cwd rules. `undefined` means the path is + * absent. + * @param path - the path to inspect; relative paths resolve against `opts.cwd`. + * @param opts - `cwd` overrides the backend's default base for relative paths. + * @param signal - aborts the metadata round-trip. + * @returns metadata only, never content; undefined for an absent path. + */ +abstract lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise + +/** + * Read the whole regular text file as a single decoded string. + * @param target - the resolved target to read. + * @param signal - aborts the read. + * @returns the full decoded UTF-8 content. + */ +abstract readText(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Stream the whole regular text file as decoded text chunks (same text + * semantics as {@link readText}, for large files). The backend owns + * cross-chunk UTF-8 decoding and binary rejection so the policy layer never + * touches raw bytes. + * @param target - the resolved target to read. + * @param signal - aborts the stream, including between chunks. + * @returns the chunk iterable, decoded and validated like {@link readText}. + */ +abstract streamText(target: FsTarget, signal?: AbortSignal): Promise> + +/** + * List direct children of a directory in stable name order. Returns resolved + * child targets plus cheap metadata only; never reads file contents. + * @param target - the resolved directory target. + * @param signal - aborts the listing. + * @returns one entry per direct child, in stable name order. + */ +abstract listDir(target: FsTarget, signal?: AbortSignal): Promise + +/** + * Atomically create or replace UTF-8 text. `expected` guards intent and + * staleness; omission allows unconditional overwrite. + * @param target - the resolved target to write. + * @param content - the full new file content. + * @param expected - the write intent guarding the write; omit for unconditional. + * @param signal - aborts before the atomic rename takes effect. + * @param sandboxPolicy - the per-call mode and workspace root this write + * runs under; a sandboxing backend fences the write by it, the bare backend + * ignores it. Omit to leave the backend its own default. + * @returns the outcome, including the version the write produced. + */ +abstract writeText( target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise + +/** + * Atomically edit literal text. When supplied, the version guard is checked + * before matching so stale content reports `FS_STALE_VERSION`; omission edits + * the current content without a freshness precondition. + * @param target - the resolved target to edit. + * @param edit - the literal search/replace request. + * @param expected - the version guard; omit for an unconditional edit. + * @param signal - aborts before the atomic rename takes effect. + * @param sandboxPolicy - the per-call mode and workspace root this edit runs + * under; a sandboxing backend fences the edit by it, the bare backend + * ignores it. Omit to leave the backend its own default. + * @returns the outcome, including the version the edit produced. + */ +abstract editText( target: FsTarget, edit: FsEditRequest, expected?: { version: FsVersion }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy, ): Promise +``` + +Types: [SandboxExecutionPolicy](sandbox.md) + +Source: [`packages/fs/fs/src/index.ts:83`](../../packages/fs/fs/src/index.ts) + + + +### `fs/*` events + + + +#### `fs/edit-intent` — waterfall + +Single-slot decision for the next FileSystem.editText. Calling `next()` yields an unconditional edit; the first returned guard wins. + +```ts cordis-catalog +/** + * Single-slot decision for the next {@link FileSystem.editText}. Calling + * `next()` yields an unconditional edit; the first returned guard wins. + * @param target - the resolved target about to be edited. + * @param actor - the opaque tool-execution context the decider keys off. + * @mode waterfall + */ +'fs/edit-intent'(target: FsTarget, actor: object | undefined, next: () => { version: FsVersion } | undefined | Promise<{ version: FsVersion } | undefined>): Promise<{ version: FsVersion } | undefined> +``` + +Source: [`packages/fs/fs/src/index.ts:64`](../../packages/fs/fs/src/index.ts) + + + +#### `fs/observed` — emit + +Record a successful observation. Listeners must be synchronous recorders: throws fail the tool call and returned promises are not awaited. + +```ts cordis-catalog +/** + * Record a successful observation. Listeners must be synchronous recorders: + * throws fail the tool call and returned promises are not awaited. + * @param target - the target that was read/written/edited. + * @param version - the version the actor now holds as its observation. + * @param actor - the observing tool-execution context; undefined records nothing useful. + * @mode emit + */ +'fs/observed'(target: FsTarget, version: FsVersion, actor: object | undefined): void +``` + +Source: [`packages/fs/fs/src/index.ts:73`](../../packages/fs/fs/src/index.ts) + + + +#### `fs/write-intent` — waterfall + +Single-slot decision for the next FileSystem.writeText. Calling `next()` yields the bare provider's unconditional write; the first listener that returns an intent owns the decision rather than composing with peers. + +```ts cordis-catalog +/** + * Single-slot decision for the next {@link FileSystem.writeText}. Calling + * `next()` yields the bare provider's unconditional write; the first listener + * that returns an intent owns the decision rather than composing with peers. + * @param target - the resolved target about to be written. + * @param actor - the opaque tool-execution context the decider keys off. + * @mode waterfall + */ +'fs/write-intent'(target: FsTarget, actor: object | undefined, next: () => FsWriteIntent | undefined | Promise): Promise +``` + +Source: [`packages/fs/fs/src/index.ts:56`](../../packages/fs/fs/src/index.ts) + diff --git a/docs/subsystems/goal.i18n.yaml b/docs/subsystems/goal.i18n.yaml index 625556f99a..945ed658d2 100644 --- a/docs/subsystems/goal.i18n.yaml +++ b/docs/subsystems/goal.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/goal.md -goal.md: fc6a7e63e58fc7cd4bc524be1e66515593680d95 -goal.zh.md: c584cb375bd55c13c158e7ee22d7721040dffa05 +# pnpm run verify-translation-pairing --write docs/subsystems/goal.md +goal.md: 6f54a5261cb44c3fda389e37cb689a00061ab241 +goal.zh.md: 043263f3231b4c727f21ca0b8fc07d8adffa0559 diff --git a/docs/subsystems/goal.md b/docs/subsystems/goal.md index fc6a7e63e5..6f54a5261c 100644 --- a/docs/subsystems/goal.md +++ b/docs/subsystems/goal.md @@ -143,3 +143,135 @@ interface GoalChanged { ## Service behavior [`GoalService`](../../packages/goal/goal/src/index.ts) resolves creation defaults, folds strict replay from durable `goal/change` events, enforces exact-live-agent identity and compare-and-set mutations, and emits contained `goal/changed` notifications. The package [README](../../packages/goal/goal/README.md) owns the callable and model-visible contract. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.goals` — `GoalService` + +Goal service (`ctx.goals`) backed exclusively by the owning session log. + +```ts cordis-catalog +/** + * Read the current goal for one exact live agent. + * @param agent - owning live agent. + * @returns a fresh view or `undefined` when no goal is current. + * @throws {@link GoalError} when the agent is not the registry's live instance. + */ +get(agent: Agent): GoalView | undefined + +/** + * Remove process-local continuation authority without changing durable goal + * phase or revision. Lifecycle owners use this before unloading a driver; + * a later human-authorized {@link resume} records the new activation edge. + * @param agent - owning live agent. + * @returns a fresh disarmed view, or `undefined` when no goal is current. + */ +disarm(agent: Agent): GoalView | undefined + +/** + * Create and arm a goal. A completed goal may be replaced; every other + * current phase must be cleared or resumed instead. + * @param agent - owning live agent. + * @param request - objective and optional round cap. + * @returns the created live view. + */ +create(agent: Agent, request: CreateGoalRequest): GoalView + +/** + * Edit objective and/or round cap without changing phase. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @param request - at least one replacement field. + * @returns the edited view. + */ +@Remote('edit') edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView + +/** + * Pause an active goal and disarm automatic continuation. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the paused view. + */ +@Remote('pause') pause(agent: Agent, ref: GoalRef): GoalView + +/** + * Resume and arm a stopped goal, or rearm an active goal after a + * session-start edge, while its round budget still has capacity. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the active view. + */ +@Remote('resume') resume(agent: Agent, ref: GoalRef): GoalView + +/** + * Mark a current non-complete goal complete and disarm it. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the completed view. + */ +@Remote('complete') complete(agent: Agent, ref: GoalRef): GoalView + +/** + * Mark an active goal blocked and disarm it. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @param reason - policy-owned stable code and human-readable explanation. + * @returns the blocked view with its durable reason. + */ +block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView + +/** + * Clear the current goal while retaining a durable tombstone and history. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the tombstone ref whose revision is one past the cleared snapshot. + */ +@Remote('clear') clear(agent: Agent, ref: GoalRef): GoalRef + +/** + * Create one Goal through the remote boundary. + * @param agent - exact live Agent resolved from the wire identity. + * @param request - objective and optional round cap. + * @returns the created Goal identity. + */ +@Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult +``` + +Types: [Agent](core.md) + +Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts) + + + +### `goal/*` events + + + +#### `goal/changed` — emit + +Goal mutation accepted by one live agent. The matching `goal/change` session event has already committed. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + +```ts cordis-catalog +/** + * Goal mutation accepted by one live agent. The matching `goal/change` + * session event has already committed. Listener failures are contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @param payload.agent - agent whose session owns the goal. + * @param payload.change - fresh current projection or clear tombstone. + * @mode emit + */ +'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped, payload: { agent: Agent; change: GoalChanged }): void +``` + +Types: [Agent](core.md) · [Scoped](scope.md) + +Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts) + diff --git a/docs/subsystems/goal.zh.md b/docs/subsystems/goal.zh.md index c584cb375b..043263f323 100644 --- a/docs/subsystems/goal.zh.md +++ b/docs/subsystems/goal.zh.md @@ -143,3 +143,135 @@ interface GoalChanged { ## 服务行为 [`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 `goal/change` 事件执行严格回放折叠、校验确切的活跃 agent 身份、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.md) 负责记录可调用契约和面向模型的契约。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.goals` — `GoalService` + +Goal service (`ctx.goals`) backed exclusively by the owning session log. + +```ts cordis-catalog +/** + * Read the current goal for one exact live agent. + * @param agent - owning live agent. + * @returns a fresh view or `undefined` when no goal is current. + * @throws {@link GoalError} when the agent is not the registry's live instance. + */ +get(agent: Agent): GoalView | undefined + +/** + * Remove process-local continuation authority without changing durable goal + * phase or revision. Lifecycle owners use this before unloading a driver; + * a later human-authorized {@link resume} records the new activation edge. + * @param agent - owning live agent. + * @returns a fresh disarmed view, or `undefined` when no goal is current. + */ +disarm(agent: Agent): GoalView | undefined + +/** + * Create and arm a goal. A completed goal may be replaced; every other + * current phase must be cleared or resumed instead. + * @param agent - owning live agent. + * @param request - objective and optional round cap. + * @returns the created live view. + */ +create(agent: Agent, request: CreateGoalRequest): GoalView + +/** + * Edit objective and/or round cap without changing phase. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @param request - at least one replacement field. + * @returns the edited view. + */ +@Remote('edit') edit(agent: Agent, ref: GoalRef, request: EditGoalRequest): GoalView + +/** + * Pause an active goal and disarm automatic continuation. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the paused view. + */ +@Remote('pause') pause(agent: Agent, ref: GoalRef): GoalView + +/** + * Resume and arm a stopped goal, or rearm an active goal after a + * session-start edge, while its round budget still has capacity. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the active view. + */ +@Remote('resume') resume(agent: Agent, ref: GoalRef): GoalView + +/** + * Mark a current non-complete goal complete and disarm it. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the completed view. + */ +@Remote('complete') complete(agent: Agent, ref: GoalRef): GoalView + +/** + * Mark an active goal blocked and disarm it. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @param reason - policy-owned stable code and human-readable explanation. + * @returns the blocked view with its durable reason. + */ +block(agent: Agent, ref: GoalRef, reason: GoalBlockReason): GoalView + +/** + * Clear the current goal while retaining a durable tombstone and history. + * @param agent - owning live agent. + * @param ref - expected current revision. + * @returns the tombstone ref whose revision is one past the cleared snapshot. + */ +@Remote('clear') clear(agent: Agent, ref: GoalRef): GoalRef + +/** + * Create one Goal through the remote boundary. + * @param agent - exact live Agent resolved from the wire identity. + * @param request - objective and optional round cap. + * @returns the created Goal identity. + */ +@Remote('create') remoteExportCreate(agent: Agent, request: CreateGoalRequest): CreateGoalResult +``` + +Types: [Agent](core.md) + +Source: [`packages/goal/goal/src/index.ts:183`](../../packages/goal/goal/src/index.ts) + + + +### `goal/*` events + + + +#### `goal/changed` — emit + +Goal mutation accepted by one live agent. The matching `goal/change` session event has already committed. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + +```ts cordis-catalog +/** + * Goal mutation accepted by one live agent. The matching `goal/change` + * session event has already committed. Listener failures are contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @param payload.agent - agent whose session owns the goal. + * @param payload.change - fresh current projection or clear tombstone. + * @mode emit + */ +'goal/changed'(this: import('@deepseek-ai/dsh-scope').Scoped, payload: { agent: Agent; change: GoalChanged }): void +``` + +Types: [Agent](core.md) · [Scoped](scope.md) + +Source: [`packages/goal/goal/src/domain.ts:114`](../../packages/goal/goal/src/domain.ts) + diff --git a/docs/subsystems/http-server.i18n.yaml b/docs/subsystems/http-server.i18n.yaml index 6f1facfad7..3674a95c44 100644 --- a/docs/subsystems/http-server.i18n.yaml +++ b/docs/subsystems/http-server.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/http-server.md -http-server.md: 200d3eed683f203b65824ea3f62cf56ce3df912f -http-server.zh.md: 9a12d3769a93270810927075be5f236875688b91 +http-server.md: b9795fe98b432b6ef5f7d01a4d3e115c809fe642 +http-server.zh.md: c3ba02dce5c58f491921f1fb31f999f9da808bc5 diff --git a/docs/subsystems/http-server.md b/docs/subsystems/http-server.md index 200d3eed68..b9795fe98b 100644 --- a/docs/subsystems/http-server.md +++ b/docs/subsystems/http-server.md @@ -45,3 +45,64 @@ interface Config { `HttpServerService` (`ctx.httpServer`) listens immediately on activation; a listen failure (EADDRINUSE…) throws out of init — a FAILED fiber the boot's fail-loud sweep reports. `register(route)` adds one named route and returns its disposer; a duplicate `(kind, path)` throws, because route patterns are a composition-level contract and a collision is a misconfiguration. `tapIndex(transform)` adds a pure html-to-html transform applied to every index response — `/` and each SPA fallback — in registration order; [dsh-client-modules](../../packages/client/modules) uses it to inject the boot manifest. `port` reads the listening port, the OS-assigned value when `config.port` is 0. A request whose handling throws (a malformed %-escape hitting `decodeURIComponent`, a client dropping mid-body) is logged as a warning and answered 400 — or the socket destroyed when headers are already out — never a process exit. Disposal pairs `close()` with `closeAllConnections()` because a handler may hold its response open (SSE) and such connections never end on their own; without the force-close, teardown would hang. The package never prints: the URL line belongs to the shell. Per-package operational detail, including the dev-mode bundle watch pipeline, stays in the [README](../../packages/host/webserver/README.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.httpServer` — `HttpServerService` + +The web-shape HTTP carrier service. Activation listens immediately (route registration order carries no request-facing semantics: named routes are composed to be disjoint, and the fallback seat answers anything not yet claimed during the boot window — 404 until its owner registers). A listen failure throws out of init — a FAILED fiber the boot's fail-loud sweep reports. + +```ts cordis-catalog +/** + * Register a named route. Duplicate (kind, path) throws — route patterns are + * a composition-level contract, so a collision is a misconfiguration. + * @param route - kind, path, and the owning handler. + * @returns the disposer removing the route. + */ +register(route: WebRoute): () => void + +/** + * Register an exact-path HTTP upgrade route. Duplicate paths throw because + * one socket can have only one protocol owner. + * @param route - pathname and handler owning negotiation plus socket use. + * @returns the disposer removing the route. + */ +registerUpgrade(route: WebUpgradeRoute): () => void + +/** + * Claim the fallback seat: the handler answering every request no named + * route matches (the SPA dist server in the shipped Web composition). One + * owner only — a second registration throws, because two fallbacks cannot + * compose. + * @param handler - owns the full response lifecycle of unmatched requests. + * @returns the disposer releasing the seat. + */ +registerFallback(handler: WebRoute['handler']): () => void + +/** + * Register an index.html transform, applied by the fallback owner to every + * index response ({@link applyIndexTaps}) in registration order. + * @param transform - pure html-to-html function. + * @returns the disposer removing the transform. + */ +tapIndex(transform: (html: string) => string): () => void + +/** + * Run an index.html body through the registered taps in registration order + * — called by the fallback owner on every index response it renders. + * @param html - the raw index.html body. + * @returns the transformed body. + */ +applyIndexTaps(html: string): string +``` + +Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts) + diff --git a/docs/subsystems/http-server.zh.md b/docs/subsystems/http-server.zh.md index 9a12d3769a..c3ba02dce5 100644 --- a/docs/subsystems/http-server.zh.md +++ b/docs/subsystems/http-server.zh.md @@ -45,3 +45,64 @@ interface Config { `HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会从 init 抛出,形成一个 FAILED fiber,由启动的大声失败 sweep 上报。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层契约,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,`config.port` 为 0 时读到的是操作系统分配的值。 处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包(package)从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.httpServer` — `HttpServerService` + +The web-shape HTTP carrier service. Activation listens immediately (route registration order carries no request-facing semantics: named routes are composed to be disjoint, and the fallback seat answers anything not yet claimed during the boot window — 404 until its owner registers). A listen failure throws out of init — a FAILED fiber the boot's fail-loud sweep reports. + +```ts cordis-catalog +/** + * Register a named route. Duplicate (kind, path) throws — route patterns are + * a composition-level contract, so a collision is a misconfiguration. + * @param route - kind, path, and the owning handler. + * @returns the disposer removing the route. + */ +register(route: WebRoute): () => void + +/** + * Register an exact-path HTTP upgrade route. Duplicate paths throw because + * one socket can have only one protocol owner. + * @param route - pathname and handler owning negotiation plus socket use. + * @returns the disposer removing the route. + */ +registerUpgrade(route: WebUpgradeRoute): () => void + +/** + * Claim the fallback seat: the handler answering every request no named + * route matches (the SPA dist server in the shipped Web composition). One + * owner only — a second registration throws, because two fallbacks cannot + * compose. + * @param handler - owns the full response lifecycle of unmatched requests. + * @returns the disposer releasing the seat. + */ +registerFallback(handler: WebRoute['handler']): () => void + +/** + * Register an index.html transform, applied by the fallback owner to every + * index response ({@link applyIndexTaps}) in registration order. + * @param transform - pure html-to-html function. + * @returns the disposer removing the transform. + */ +tapIndex(transform: (html: string) => string): () => void + +/** + * Run an index.html body through the registered taps in registration order + * — called by the fallback owner on every index response it renders. + * @param html - the raw index.html body. + * @returns the transformed body. + */ +applyIndexTaps(html: string): string +``` + +Source: [`packages/host/webserver/src/index.ts:60`](../../packages/host/webserver/src/index.ts) + diff --git a/docs/subsystems/invariants.i18n.yaml b/docs/subsystems/invariants.i18n.yaml index 27b6ac8e87..8ef657b3a4 100644 --- a/docs/subsystems/invariants.i18n.yaml +++ b/docs/subsystems/invariants.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/invariants.md -invariants.md: 143a7feabbab18556f595a78c7aef17caa6d3028 -invariants.zh.md: fb497857c02202544edf44ccfe3a2935a988e30e +invariants.md: 184d982bb27ab3ff98f715f0751c8bfcc1d08bea +invariants.zh.md: bd2f83a018642832fe50ba5cca6dd82e2bd7d59b diff --git a/docs/subsystems/invariants.md b/docs/subsystems/invariants.md index 143a7feabb..184d982bb2 100644 --- a/docs/subsystems/invariants.md +++ b/docs/subsystems/invariants.md @@ -57,3 +57,32 @@ An enabled installer runs in a dedicated child Cordis fiber; `installer.inject` ## The companion contract Every workspace package owns a `./invariant` companion ([package contract](../../packages/AGENTS.md)); publication and registration are exhaustive, but assertions are deliberately not synthetic. A companion installs a check only when its package owns an observable event or mutable-data relationship; otherwise it exports an empty installer whose leading comment starts `No runtime invariant:` and explains, package-specifically, why nothing is checkable. `pnpm run verify-package-invariants` mechanically rejects generated markers, unexplained empty installers, non-empty installers that omit or ignore the reporter, incorrect registration names, and incomplete export, publication, dependency, or bundle wiring ([mechanical-rule Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md)). The catalog of executable companions and the standard composition live in the [package README](../../packages/support/invariants/README.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.invariants` — `InvariantService` + +Package-owned invariant registry with global and regex-based selection. + +```ts cordis-catalog +/** + * Register one package's invariant installer. The package name is reserved + * even when filtering disables its checks. Enabled installers run in a child + * fiber; failure disposes that fiber and releases the reservation. + * @param packageName - full npm package name that owns the contribution. + * @param installer - listener or startup-check installer for the child context. + * @returns an effect-scoped disposer for the registration. + */ +register(packageName: string, installer: InvariantInstaller): () => void +``` + +Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts) + diff --git a/docs/subsystems/invariants.zh.md b/docs/subsystems/invariants.zh.md index fb497857c0..bd2f83a018 100644 --- a/docs/subsystems/invariants.zh.md +++ b/docs/subsystems/invariants.zh.md @@ -57,3 +57,32 @@ interface InvariantInstaller { ## 配套插件契约 每个工作区包都拥有一个 `./invariant` 配套插件([包契约](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包对某个可观察事件或可变数据拥有所有权关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.invariants` — `InvariantService` + +Package-owned invariant registry with global and regex-based selection. + +```ts cordis-catalog +/** + * Register one package's invariant installer. The package name is reserved + * even when filtering disables its checks. Enabled installers run in a child + * fiber; failure disposes that fiber and releases the reservation. + * @param packageName - full npm package name that owns the contribution. + * @param installer - listener or startup-check installer for the child context. + * @returns an effect-scoped disposer for the registration. + */ +register(packageName: string, installer: InvariantInstaller): () => void +``` + +Source: [`packages/support/invariants/src/index.ts:94`](../../packages/support/invariants/src/index.ts) + diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index 1ff1a1d42b..bd1d833f51 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md -llm-streaming.md: 5c90b3ce4ac65a99997f6ba7ad5deac494b7f772 -llm-streaming.zh.md: 7fd0043234cb40d6b21cec6ff101993164785a6e +llm-streaming.md: fe4a831660dd81b33a1cd7dc9a9d7026f3831991 +llm-streaming.zh.md: a10b696ec0887e139785fc7e95d8641e6496de79 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index 5c90b3ce4a..fe4a831660 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -255,3 +255,191 @@ interface ContentBlockMap { ``` See [core.md § Content blocks and messages](core.md#content-blocks-and-messages) for the block interfaces. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.llm` — `LlmService` + +The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. + +```ts cordis-catalog +/** + * Register an adapter for the given provider routes. Throws `LlmError` with code + * `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing). + * Disposed with the fiber. + * @param providers - every provider route this adapter should serve. + * @param adapter - the adapter that streams calls for those providers. + * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}. + */ +registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle + +/** + * Describe provider routes with a registered adapter. + * @returns detached provider metadata in registration order. + */ +listProviders(): LlmProviderInfo[] + +/** + * Declare provider routes an adapter plugin can activate through + * configuration. Registration is all-or-nothing: an empty list, invalid + * entry, or a provider already declared by any registration throws + * `LlmError` without registering the rest. Disposed with the fiber. + * @param entries - every configurable provider this plugin owns. + * @returns a handle that withdraws all of them, and can atomically replace them. + */ +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle + +/** + * List every declared configurable provider, registered or dormant. + * @returns detached directory entries in declaration order. + */ +listConfigurableProviders(): LlmConfigurableProvider[] + +/** + * Offer to interrogate provider endpoints on behalf of the settings + * namespace this plugin owns. The namespace is the key because that is what + * a configuration surface already holds from the configurable-provider + * directory, and because a provider being *added* has no route to name yet. + * Disposed with the fiber. + * @param settingsNs - the namespace whose profiles this discovery serves. + * @param discover - interrogates one endpoint; must honor `request.signal`. + * @returns the disposer that withdraws the offer. + */ +registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void + +/** + * Interrogate one provider endpoint for the models it advertises. The + * request describes a draft, not a stored route, so nothing here reads or + * writes settings or credentials — the caller owns both, and the reply is + * candidate metadata a surface may offer for adoption. + * @param settingsNs - namespace whose registered discovery serves this draft. + * @param request - the endpoint, protocol, and one-shot credential to use. + * @returns the advertised models, deduplicated in endpoint order. + */ +async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise + +/** + * Resolve the retry policy captured when one provider route was registered. + * @param provider - registered provider route to inspect. + * @returns the provider-owned policy, with normal defaults already resolved. + */ +providerRetryPolicy(provider: string): ResolvedRetryPolicy + +/** + * Discover models advertised by one registered provider. Catalog membership + * is advisory and never changes routing or request validation. + * @param provider - registered provider route to inspect. + * @returns detached model metadata in adapter-preferred order. + */ +async listModels(provider: string): Promise + +/** + * Resolve and validate all metadata from the adapter that owns one exact + * route. The result is detached from adapter-owned objects; catalog + * membership remains advisory and does not control request routing. + * @param provider - registered provider route to inspect. + * @param model - exact model id passed to the adapter. + * @param signal - optional cancellation for adapter-owned asynchronous lookup. + * @returns exact model identity plus available context and reasoning metadata. + */ +async resolveModelInfo( provider: string, model: string, signal?: AbortSignal, ): Promise + +/** + * Validate a conversation call config against its exact model capability and + * materialize adapter-configured defaults. Unsupported explicit efforts + * reject before provider I/O; no clamping or aliasing is performed. This + * standalone query does not bind a later dispatch; use {@link prepareCall} + * when logging and streaming must share one adapter registration. + * @param config - provider/model route and optional request controls. + * @param signal - optional cancellation for adapter-owned capability lookup. + * @returns a detached config only when a default must be materialized. + */ +async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise + +/** + * Resolve one call under its current adapter registration. The returned + * one-shot handle keeps that registration across header logging and dispatch, + * so HMR cannot combine one adapter's capability result with another adapter. + * @param config - provider/model route and optional request controls. + * @param signal - optional cancellation for adapter-owned capability lookup. + * @returns a prepared config and its registration-bound stream entry point. + */ +async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise + +/** + * Stream one model call as raw chunks (token-level deltas). Replay state is + * retained only when the same adapter instance owns its historical provider + * and the target provider. Final adapter selection remains fixed through + * asynchronous exact-model resolution and dispatch. Adapter selection, + * dispatch, and iteration failures become terminal `error` or `aborted` + * finish chunks; middleware, nested-call, cleanup, and consumer failures + * remain thrown. + * @param options - the full request; `options.provider` selects the adapter. + * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. + */ +stream(options: GenerateOptions): AsyncIterable +``` + +Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) + +Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) + + + +### `llm/*` events + + + +#### `llm/adapters-updated` — emit + +The provider topology changed: an adapter registered or unregistered routes, or the configurable-provider directory gained or lost entries. This is a payload-free registry notification fired at each commit point (including registration disposal); consumers re-read `listProviders()`, `listModels()`, or `listConfigurableProviders()` for the new state. Observer failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * The provider topology changed: an adapter registered or unregistered + * routes, or the configurable-provider directory gained or lost entries. + * This is a payload-free registry notification fired at each commit point + * (including registration disposal); consumers re-read `listProviders()`, + * `listModels()`, or `listConfigurableProviders()` for the new state. + * Observer failures are contained and cannot veto the registry mutation. + * @mode emit + */ +'llm/adapters-updated'(): void +``` + +Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) + + + +#### `llm/stream` — waterfall + +Waterfall around every streaming model call (retry, replay, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit. + +```ts cordis-catalog +/** + * Waterfall around every streaming model call (retry, replay, routing). + * Bound to the {@link LlmService}; call `next()` to reach the resolved + * adapter's stream, or yield your own chunks to short-circuit. + * @param options - the full request. A LOOP-built request carries the + * process-local {@link markAgentLoopRequest} identity and arrives deep-frozen + * (mutation throws): its content is a pure function of the session log (the + * reconstructability Agent Note), so listeners read it, never rewrite it. + * Hand-built calls do not carry that marker; their messages already obey + * the immutable creation contract. + * @mode waterfall + */ +'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable +``` + +Types: [GenerateOptions](core.md) + +Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) + diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index 7fd0043234..a10b696ec0 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -255,3 +255,191 @@ interface ContentBlockMap { ``` 块接口详见 [core.md § Content blocks and messages](core.md#content-blocks-and-messages)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.llm` — `LlmService` + +The abstract `llm` service: an adapter registry plus a streaming model-call surface, interceptable via the `llm/stream` waterfall. + +```ts cordis-catalog +/** + * Register an adapter for the given provider routes. Throws `LlmError` with code + * `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing). + * Disposed with the fiber. + * @param providers - every provider route this adapter should serve. + * @param adapter - the adapter that streams calls for those providers. + * @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}. + */ +registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle + +/** + * Describe provider routes with a registered adapter. + * @returns detached provider metadata in registration order. + */ +listProviders(): LlmProviderInfo[] + +/** + * Declare provider routes an adapter plugin can activate through + * configuration. Registration is all-or-nothing: an empty list, invalid + * entry, or a provider already declared by any registration throws + * `LlmError` without registering the rest. Disposed with the fiber. + * @param entries - every configurable provider this plugin owns. + * @returns a handle that withdraws all of them, and can atomically replace them. + */ +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle + +/** + * List every declared configurable provider, registered or dormant. + * @returns detached directory entries in declaration order. + */ +listConfigurableProviders(): LlmConfigurableProvider[] + +/** + * Offer to interrogate provider endpoints on behalf of the settings + * namespace this plugin owns. The namespace is the key because that is what + * a configuration surface already holds from the configurable-provider + * directory, and because a provider being *added* has no route to name yet. + * Disposed with the fiber. + * @param settingsNs - the namespace whose profiles this discovery serves. + * @param discover - interrogates one endpoint; must honor `request.signal`. + * @returns the disposer that withdraws the offer. + */ +registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void + +/** + * Interrogate one provider endpoint for the models it advertises. The + * request describes a draft, not a stored route, so nothing here reads or + * writes settings or credentials — the caller owns both, and the reply is + * candidate metadata a surface may offer for adoption. + * @param settingsNs - namespace whose registered discovery serves this draft. + * @param request - the endpoint, protocol, and one-shot credential to use. + * @returns the advertised models, deduplicated in endpoint order. + */ +async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise + +/** + * Resolve the retry policy captured when one provider route was registered. + * @param provider - registered provider route to inspect. + * @returns the provider-owned policy, with normal defaults already resolved. + */ +providerRetryPolicy(provider: string): ResolvedRetryPolicy + +/** + * Discover models advertised by one registered provider. Catalog membership + * is advisory and never changes routing or request validation. + * @param provider - registered provider route to inspect. + * @returns detached model metadata in adapter-preferred order. + */ +async listModels(provider: string): Promise + +/** + * Resolve and validate all metadata from the adapter that owns one exact + * route. The result is detached from adapter-owned objects; catalog + * membership remains advisory and does not control request routing. + * @param provider - registered provider route to inspect. + * @param model - exact model id passed to the adapter. + * @param signal - optional cancellation for adapter-owned asynchronous lookup. + * @returns exact model identity plus available context and reasoning metadata. + */ +async resolveModelInfo( provider: string, model: string, signal?: AbortSignal, ): Promise + +/** + * Validate a conversation call config against its exact model capability and + * materialize adapter-configured defaults. Unsupported explicit efforts + * reject before provider I/O; no clamping or aliasing is performed. This + * standalone query does not bind a later dispatch; use {@link prepareCall} + * when logging and streaming must share one adapter registration. + * @param config - provider/model route and optional request controls. + * @param signal - optional cancellation for adapter-owned capability lookup. + * @returns a detached config only when a default must be materialized. + */ +async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise + +/** + * Resolve one call under its current adapter registration. The returned + * one-shot handle keeps that registration across header logging and dispatch, + * so HMR cannot combine one adapter's capability result with another adapter. + * @param config - provider/model route and optional request controls. + * @param signal - optional cancellation for adapter-owned capability lookup. + * @returns a prepared config and its registration-bound stream entry point. + */ +async prepareCall(config: LlmCallConfig, signal?: AbortSignal): Promise + +/** + * Stream one model call as raw chunks (token-level deltas). Replay state is + * retained only when the same adapter instance owns its historical provider + * and the target provider. Final adapter selection remains fixed through + * asynchronous exact-model resolution and dispatch. Adapter selection, + * dispatch, and iteration failures become terminal `error` or `aborted` + * finish chunks; middleware, nested-call, cleanup, and consumer failures + * remain thrown. + * @param options - the full request; `options.provider` selects the adapter. + * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. + */ +stream(options: GenerateOptions): AsyncIterable +``` + +Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) + +Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) + + + +### `llm/*` events + + + +#### `llm/adapters-updated` — emit + +The provider topology changed: an adapter registered or unregistered routes, or the configurable-provider directory gained or lost entries. This is a payload-free registry notification fired at each commit point (including registration disposal); consumers re-read `listProviders()`, `listModels()`, or `listConfigurableProviders()` for the new state. Observer failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * The provider topology changed: an adapter registered or unregistered + * routes, or the configurable-provider directory gained or lost entries. + * This is a payload-free registry notification fired at each commit point + * (including registration disposal); consumers re-read `listProviders()`, + * `listModels()`, or `listConfigurableProviders()` for the new state. + * Observer failures are contained and cannot veto the registry mutation. + * @mode emit + */ +'llm/adapters-updated'(): void +``` + +Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) + + + +#### `llm/stream` — waterfall + +Waterfall around every streaming model call (retry, replay, routing). Bound to the LlmService; call `next()` to reach the resolved adapter's stream, or yield your own chunks to short-circuit. + +```ts cordis-catalog +/** + * Waterfall around every streaming model call (retry, replay, routing). + * Bound to the {@link LlmService}; call `next()` to reach the resolved + * adapter's stream, or yield your own chunks to short-circuit. + * @param options - the full request. A LOOP-built request carries the + * process-local {@link markAgentLoopRequest} identity and arrives deep-frozen + * (mutation throws): its content is a pure function of the session log (the + * reconstructability Agent Note), so listeners read it, never rewrite it. + * Hand-built calls do not carry that marker; their messages already obey + * the immutable creation contract. + * @mode waterfall + */ +'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable +``` + +Types: [GenerateOptions](core.md) + +Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) + diff --git a/docs/subsystems/permission.i18n.yaml b/docs/subsystems/permission.i18n.yaml index c63da5efdc..6f6ba3b3c3 100644 --- a/docs/subsystems/permission.i18n.yaml +++ b/docs/subsystems/permission.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/permission.md -permission.md: ec8cd4a923b617934f1612c31284103266aea4ef -permission.zh.md: 4534c33e2715e4a500344f547c7895128877f4fb +permission.md: 60ed88a79d0e51922f4cdffafa8b547669b49f5d +permission.zh.md: c06714fc6a111f56af680c3dea1764c90f09998d diff --git a/docs/subsystems/permission.md b/docs/subsystems/permission.md index ec8cd4a923..60ed88a79d 100644 --- a/docs/subsystems/permission.md +++ b/docs/subsystems/permission.md @@ -65,4 +65,67 @@ interface PresetOption { `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](#ctxpermission--permissionservice). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.permission` — `PermissionService` + +Owns the deployment's permission presets and their write path. Requires a confining `ctx.bash` executor and `ctx.approval`; unmatched knob values are reported as CUSTOM_PRESET, not an error. + +```ts cordis-catalog +/** + * Resolve the preset matching the effective knob values. A still-matching + * last selection wins shared-bundle ties; otherwise the first table match + * wins, or {@link CUSTOM_PRESET} when no entry matches. + * @param events - the session's events in log order. + * @returns the effective preset name, or `custom` when nothing matches. + */ +current(events: readonly SessionEvent[]): string + +/** + * Build the whole select value for one folded knob state: every table + * option in declaration order, `custom` appended exactly while derived. + * @param state - the folded knob overrides. + * @returns the `permissions` projection payload. + */ +selectFor(state: KnobState): PermissionSelect + +/** + * Resolve a preset's knob bundle. + * @param name - the preset name to resolve. + * @returns the configured bundle. + * @throws when `name` is not in the table. + */ +resolve(name: string): PresetSpec + +/** + * Build the client option for a table entry or {@link CUSTOM_PRESET}. A + * missing label falls back to the table key. + * @param name - a table key, or `custom`. + * @returns the option a client renders. + * @throws when `name` is neither a table key nor `custom`. + */ +optionOf(name: string): PresetOption + +/** + * Record a changed preset, then update each changed knob through its own + * setter. Selecting the effective preset again appends nothing. + * @param session - the session the switch belongs to. + * @param name - the preset to switch to; unknown names throw. + */ +set(session: Session, name: string): void +``` + +Types: [Session](session.md) · [SessionEvent](core.md) + +Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts) + diff --git a/docs/subsystems/permission.zh.md b/docs/subsystems/permission.zh.md index 4534c33e27..c06714fc6a 100644 --- a/docs/subsystems/permission.zh.md +++ b/docs/subsystems/permission.zh.md @@ -65,4 +65,67 @@ interface PresetOption { `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);方法签名见生成的[服务目录](#ctxpermission--permissionservice)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.permission` — `PermissionService` + +Owns the deployment's permission presets and their write path. Requires a confining `ctx.bash` executor and `ctx.approval`; unmatched knob values are reported as CUSTOM_PRESET, not an error. + +```ts cordis-catalog +/** + * Resolve the preset matching the effective knob values. A still-matching + * last selection wins shared-bundle ties; otherwise the first table match + * wins, or {@link CUSTOM_PRESET} when no entry matches. + * @param events - the session's events in log order. + * @returns the effective preset name, or `custom` when nothing matches. + */ +current(events: readonly SessionEvent[]): string + +/** + * Build the whole select value for one folded knob state: every table + * option in declaration order, `custom` appended exactly while derived. + * @param state - the folded knob overrides. + * @returns the `permissions` projection payload. + */ +selectFor(state: KnobState): PermissionSelect + +/** + * Resolve a preset's knob bundle. + * @param name - the preset name to resolve. + * @returns the configured bundle. + * @throws when `name` is not in the table. + */ +resolve(name: string): PresetSpec + +/** + * Build the client option for a table entry or {@link CUSTOM_PRESET}. A + * missing label falls back to the table key. + * @param name - a table key, or `custom`. + * @returns the option a client renders. + * @throws when `name` is neither a table key nor `custom`. + */ +optionOf(name: string): PresetOption + +/** + * Record a changed preset, then update each changed knob through its own + * setter. Selecting the effective preset again appends nothing. + * @param session - the session the switch belongs to. + * @param name - the preset to switch to; unknown names throw. + */ +set(session: Session, name: string): void +``` + +Types: [Session](session.md) · [SessionEvent](core.md) + +Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts) + diff --git a/docs/subsystems/persistence.i18n.yaml b/docs/subsystems/persistence.i18n.yaml index 697094b490..808abb402a 100644 --- a/docs/subsystems/persistence.i18n.yaml +++ b/docs/subsystems/persistence.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/persistence.md -persistence.md: 177d596d3d01134266f139b47430e1e5064d04d6 -persistence.zh.md: 6582c0d7d02d1976d5c3ca3322284098130ed4ad +persistence.md: 1edf3c88fbabe55058bbf94ed8f31decf47cb84a +persistence.zh.md: dbebd9ff819c8d50cfa9748678dac09c86adde6d diff --git a/docs/subsystems/persistence.md b/docs/subsystems/persistence.md index 177d596d3d..1edf3c88fb 100644 --- a/docs/subsystems/persistence.md +++ b/docs/subsystems/persistence.md @@ -204,3 +204,135 @@ Both implement the same abstract `SessionPersistence` (locate/create/append/prep - **[dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl)** — an append-only logical JSONL log per session, stored as checksummed concatenated Zstandard frames by default or raw lines by configuration, with crash-safe atomic writes, interrupted-turn recovery, and a read/replay path. - **[dsh-session-persistence-sqlite](../../packages/session/session-persistence-sqlite)** — `node:sqlite`, one row per `SessionEvent`. The row shape `(session_id, seq, type, time, data, source_event_seqs, surface_op)` maps 1:1 onto the event, including optional surface metadata, so there is no parallel persisted schema to keep in sync. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) + +Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events. + +```ts cordis-catalog +/** + * Resolve this backend's independent local artifact for a session without + * reading, creating, flushing, or otherwise materializing it. Backends such + * as SQLite that do not own one artifact per session return `undefined`. + * @param meta - the immutable session header whose artifact is requested. + * @returns the backend-specific absolute location, when one exists. + */ +abstract locate(meta: SessionHeader): SessionLocation | undefined + +/** + * Register a new session's metadata. A backend MAY defer the physical write + * until the first {@link append} (lazy materialization), in which case a + * created-but-never-appended session is absent from {@link list} + * — abandoned sessions leave nothing behind. + * @param meta - the immutable header (id, version, cwd, lineage) to record. + */ +abstract create(meta: SessionHeader): Promise + +/** + * Durably persist a batch of events. Honors the append-only and contiguous- + * seq contracts: the first event's `seq` MUST equal the stored next-seq + * (after `load` has durably closed any interrupted turn). Rejects non-JSON- + * serializable `event.data` with an error naming the offending event type. + * @param id - the session the batch belongs to. + * @param events - the contiguous batch to persist, in seq order. + */ +abstract append(id: SessionId, events: readonly SessionEvent[]): Promise + +/** + * Prepare the exact unpublished Session used by resume. Implementations may + * reuse object graphs retained by an earlier {@link inspect} after confirming + * their durable revision is still current; disposal releases an unpublished + * reservation. Revision retries require the durable log to remain unchanged + * for one read/check round trip; continuous external writers may delay completion. + * @param id - persisted session to prepare. + * @param signal - optional cancellation for preparation work. + * @returns one owned unpublished Session preparation. + */ +async prepare(id: SessionId, signal?: AbortSignal): Promise + +/** + * Load an immutable balanced logical view and commit any required cold + * recovery. A complete interrupted final turn is preserved and durably + * closed with missing tool errors plus any open step and turn boundaries; + * only a torn final record is discarded. Unknown versions and corruption in + * the committed prefix reject. Implementations MUST NOT crash-repair an + * identity still bound to a live Session: a balanced live log may return as a + * durable snapshot, while an open live turn rejects. Returned values may be + * shared with immutable live or prepared state and must not be mutated. + * Revision-based implementations may wait for one stable read/check round trip. + * @param id - the persisted session to reload. + * @returns the header and a log ending on a balanced `turn/end`. + */ +abstract load(id: SessionId): Promise + +/** + * Inspect an immutable logical session without committing recovery or + * publishing it. A cold complete interrupted turn receives synthetic closers + * in memory and a torn physical tail remains untouched. An already-live + * Session instead yields its current immutable snapshot, which may contain an + * open turn and its `session/end-seed` boundary. Coordinator-backed + * implementations retain the exact cold unpublished Session for bounded + * reuse by a later {@link prepare}. A stale ready source is reloaded; a source + * already committing or reserved for resume remains exclusive, and inspection + * may borrow its immutable view. Callers borrow only the immutable header and + * log. Continuous external writers may delay revision convergence. + * @param id - the persisted session to inspect. + * @param signal - optional cancellation for queued and backend read work. + * @returns the validated header and current logical event log. + */ +abstract inspect(id: SessionId, signal?: AbortSignal): Promise + +/** + * Read the stored events from `fromSeq` onward — the read-from-seq + * primitive for read models that resume from a watermark (e.g. a persisted + * projection cache folding only the tail past its checkpoint). Unlike + * {@link inspect}, it is a detached physical suffix read: no preparation + * cache, torn-tail truncation, synthetic closers, or coordinator-state + * publication. Only events from the valid contiguous stored prefix are + * returned, so a torn fragment never reaches the caller. `fromSeq` at or + * beyond the stored prefix returns an empty event list (never an error). + * Backends whose medium can seek by seq + * (SQLite) read only the suffix; sequential media (JSONL, both encodings) + * still parse the whole artifact and skip forward — the primitive bounds + * what is RETURNED and refolded, not every backend's physical read. + * @param id - the persisted session to read. + * @param fromSeq - first event seq to include; a non-negative safe integer. + * @param signal - optional cancellation for queued and backend read work. + * @returns the header and the stored events with `seq >= fromSeq`. + */ +abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> + +/** + * Lightweight listing from metadata, without a full-log parse. + * @param signal - optional cancellation for backend listing work. + * @returns one header per materialized session. + */ +abstract list(signal?: AbortSignal): Promise + +/** + * List materialized sessions with cheap per-log change tokens. + * + * Repeated observations of an unchanged log return the same revision. A + * successful mutating {@link load} repair changes the next listed revision. + * Revisions also distinguish independently backed stores so backend-local + * counters cannot compare equal across different persistence sources. + * @param signal - optional cancellation for backend snapshot-listing work. + * @returns one header and opaque revision per materialized session without loading full logs. + */ +abstract listSnapshots(signal?: AbortSignal): Promise +``` + +Types: [SessionEvent](core.md) · [SessionId](core.md) + +Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) + diff --git a/docs/subsystems/persistence.zh.md b/docs/subsystems/persistence.zh.md index 6582c0d7d0..dbebd9ff81 100644 --- a/docs/subsystems/persistence.zh.md +++ b/docs/subsystems/persistence.zh.md @@ -204,3 +204,135 @@ interface SessionPersistenceSnapshot { - **[dsh-session-persistence-jsonl](../../packages/session/session-persistence-jsonl)**——每个会话一份仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;支持崩溃安全的原子写入、被中断轮次的恢复以及读取/回放路径。 - **[dsh-session-persistence-sqlite](../../packages/session/session-persistence-sqlite)**:基于 `node:sqlite`,每个 `SessionEvent` 一行。行结构 `(session_id, seq, type, time, data, source_event_seqs, surface_op)` 与事件 1:1 映射(包含可选的 surface 元数据),因此没有需要保持同步的并行持久化 schema。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam) + +Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events. + +```ts cordis-catalog +/** + * Resolve this backend's independent local artifact for a session without + * reading, creating, flushing, or otherwise materializing it. Backends such + * as SQLite that do not own one artifact per session return `undefined`. + * @param meta - the immutable session header whose artifact is requested. + * @returns the backend-specific absolute location, when one exists. + */ +abstract locate(meta: SessionHeader): SessionLocation | undefined + +/** + * Register a new session's metadata. A backend MAY defer the physical write + * until the first {@link append} (lazy materialization), in which case a + * created-but-never-appended session is absent from {@link list} + * — abandoned sessions leave nothing behind. + * @param meta - the immutable header (id, version, cwd, lineage) to record. + */ +abstract create(meta: SessionHeader): Promise + +/** + * Durably persist a batch of events. Honors the append-only and contiguous- + * seq contracts: the first event's `seq` MUST equal the stored next-seq + * (after `load` has durably closed any interrupted turn). Rejects non-JSON- + * serializable `event.data` with an error naming the offending event type. + * @param id - the session the batch belongs to. + * @param events - the contiguous batch to persist, in seq order. + */ +abstract append(id: SessionId, events: readonly SessionEvent[]): Promise + +/** + * Prepare the exact unpublished Session used by resume. Implementations may + * reuse object graphs retained by an earlier {@link inspect} after confirming + * their durable revision is still current; disposal releases an unpublished + * reservation. Revision retries require the durable log to remain unchanged + * for one read/check round trip; continuous external writers may delay completion. + * @param id - persisted session to prepare. + * @param signal - optional cancellation for preparation work. + * @returns one owned unpublished Session preparation. + */ +async prepare(id: SessionId, signal?: AbortSignal): Promise + +/** + * Load an immutable balanced logical view and commit any required cold + * recovery. A complete interrupted final turn is preserved and durably + * closed with missing tool errors plus any open step and turn boundaries; + * only a torn final record is discarded. Unknown versions and corruption in + * the committed prefix reject. Implementations MUST NOT crash-repair an + * identity still bound to a live Session: a balanced live log may return as a + * durable snapshot, while an open live turn rejects. Returned values may be + * shared with immutable live or prepared state and must not be mutated. + * Revision-based implementations may wait for one stable read/check round trip. + * @param id - the persisted session to reload. + * @returns the header and a log ending on a balanced `turn/end`. + */ +abstract load(id: SessionId): Promise + +/** + * Inspect an immutable logical session without committing recovery or + * publishing it. A cold complete interrupted turn receives synthetic closers + * in memory and a torn physical tail remains untouched. An already-live + * Session instead yields its current immutable snapshot, which may contain an + * open turn and its `session/end-seed` boundary. Coordinator-backed + * implementations retain the exact cold unpublished Session for bounded + * reuse by a later {@link prepare}. A stale ready source is reloaded; a source + * already committing or reserved for resume remains exclusive, and inspection + * may borrow its immutable view. Callers borrow only the immutable header and + * log. Continuous external writers may delay revision convergence. + * @param id - the persisted session to inspect. + * @param signal - optional cancellation for queued and backend read work. + * @returns the validated header and current logical event log. + */ +abstract inspect(id: SessionId, signal?: AbortSignal): Promise + +/** + * Read the stored events from `fromSeq` onward — the read-from-seq + * primitive for read models that resume from a watermark (e.g. a persisted + * projection cache folding only the tail past its checkpoint). Unlike + * {@link inspect}, it is a detached physical suffix read: no preparation + * cache, torn-tail truncation, synthetic closers, or coordinator-state + * publication. Only events from the valid contiguous stored prefix are + * returned, so a torn fragment never reaches the caller. `fromSeq` at or + * beyond the stored prefix returns an empty event list (never an error). + * Backends whose medium can seek by seq + * (SQLite) read only the suffix; sequential media (JSONL, both encodings) + * still parse the whole artifact and skip forward — the primitive bounds + * what is RETURNED and refolded, not every backend's physical read. + * @param id - the persisted session to read. + * @param fromSeq - first event seq to include; a non-negative safe integer. + * @param signal - optional cancellation for queued and backend read work. + * @returns the header and the stored events with `seq >= fromSeq`. + */ +abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }> + +/** + * Lightweight listing from metadata, without a full-log parse. + * @param signal - optional cancellation for backend listing work. + * @returns one header per materialized session. + */ +abstract list(signal?: AbortSignal): Promise + +/** + * List materialized sessions with cheap per-log change tokens. + * + * Repeated observations of an unchanged log return the same revision. A + * successful mutating {@link load} repair changes the next listed revision. + * Revisions also distinguish independently backed stores so backend-local + * counters cannot compare equal across different persistence sources. + * @param signal - optional cancellation for backend snapshot-listing work. + * @returns one header and opaque revision per materialized session without loading full logs. + */ +abstract listSnapshots(signal?: AbortSignal): Promise +``` + +Types: [SessionEvent](core.md) · [SessionId](core.md) + +Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) + diff --git a/docs/subsystems/plan.i18n.yaml b/docs/subsystems/plan.i18n.yaml index f82002420b..efe9e376c6 100644 --- a/docs/subsystems/plan.i18n.yaml +++ b/docs/subsystems/plan.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/plan.md -plan.md: 661a393b18f9154771c0b9358ad7f016a4fdec17 -plan.zh.md: 068b63fd9f3bf46b7962031bba154cc7fc7daafd +plan.md: ee4b911050863765960ba46952ba923a7115e129 +plan.zh.md: 0481bdf76adf2f00fe18073fe8daa5ba172bbfe1 diff --git a/docs/subsystems/plan.md b/docs/subsystems/plan.md index 661a393b18..ee4b911050 100644 --- a/docs/subsystems/plan.md +++ b/docs/subsystems/plan.md @@ -36,4 +36,51 @@ When [`ctx.commands`](commands.md) is composed, the plugin registers `/plan [off ## The service -`ctx.planMode` owns the logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool; `get`/`set` signatures are in the generated [service catalog](../cordis-catalog/services.md#ctxplanmode--planmodeservice). +`ctx.planMode` owns the logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool; `get`/`set` signatures are in the generated [service catalog](#ctxplanmode--planmodeservice). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.planMode` — `PlanModeService` + +`ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror. + +```ts cordis-catalog +/** + * Read the logged plan state and any selected state awaiting a boundary. + * + * @param agent The agent to read. + * @returns Current logged state plus a pending selection, when present. + */ +get(agent: Agent): { active: boolean; pending?: boolean } + +/** + * Select whether plan mode should be active. Between turns the change + * commits immediately — no request boundary would arrive until the next + * prompt, so a queued intent would hang (the open-turn fold is the idle + * signal: agent status stays `running` through post-turn checkpointing, + * where a boundary equally never comes). During an open turn the + * selection is held as pending intent for the next in-turn request + * boundary. Repeated selection of the current or already-pending state is + * a no-op. + * + * @param agent The agent to switch. + * @param active Whether plan mode should be active. + * @returns what happened: `committed` (logged now), `queued` (awaiting the + * next boundary), `cancelled` (an opposite pending selection was cleared; + * the logged state already matches), or `noop` (already in that state). + */ +set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' +``` + +Types: [Agent](core.md) + +Source: [`packages/plan/plan-mode/src/index.ts:183`](../../packages/plan/plan-mode/src/index.ts) + diff --git a/docs/subsystems/plan.zh.md b/docs/subsystems/plan.zh.md index 068b63fd9f..0481bdf76a 100644 --- a/docs/subsystems/plan.zh.md +++ b/docs/subsystems/plan.zh.md @@ -36,4 +36,51 @@ interface PlanModeConfig { ## 服务 -`ctx.planMode` 拥有已记录的计划状态、边界处的应用与叙述、`plan:policy` 段落、`/plan` 命令和稳定注册的退出工具;`get`/`set` 签名见生成的[服务目录](../cordis-catalog/services.md#ctxplanmode--planmodeservice)。 +`ctx.planMode` 拥有已记录的计划状态、边界处的应用与叙述、`plan:policy` 段落、`/plan` 命令和稳定注册的退出工具;`get`/`set` 签名见生成的[服务目录](#ctxplanmode--planmodeservice)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.planMode` — `PlanModeService` + +`ctx.planMode`: owns logged plan state, boundary application and narration, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror. + +```ts cordis-catalog +/** + * Read the logged plan state and any selected state awaiting a boundary. + * + * @param agent The agent to read. + * @returns Current logged state plus a pending selection, when present. + */ +get(agent: Agent): { active: boolean; pending?: boolean } + +/** + * Select whether plan mode should be active. Between turns the change + * commits immediately — no request boundary would arrive until the next + * prompt, so a queued intent would hang (the open-turn fold is the idle + * signal: agent status stays `running` through post-turn checkpointing, + * where a boundary equally never comes). During an open turn the + * selection is held as pending intent for the next in-turn request + * boundary. Repeated selection of the current or already-pending state is + * a no-op. + * + * @param agent The agent to switch. + * @param active Whether plan mode should be active. + * @returns what happened: `committed` (logged now), `queued` (awaiting the + * next boundary), `cancelled` (an opposite pending selection was cleared; + * the logged state already matches), or `noop` (already in that state). + */ +set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop' +``` + +Types: [Agent](core.md) + +Source: [`packages/plan/plan-mode/src/index.ts:183`](../../packages/plan/plan-mode/src/index.ts) + diff --git a/docs/subsystems/pty.i18n.yaml b/docs/subsystems/pty.i18n.yaml index 11788e8e76..76b4500aea 100644 --- a/docs/subsystems/pty.i18n.yaml +++ b/docs/subsystems/pty.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/pty.md -pty.md: 97e1e662d1128ab0555e34f8284cf69d7d9d0d1a -pty.zh.md: a57f7448274de583dbb110ba9499e15dfb4de5f9 +# pnpm run verify-translation-pairing --write docs/subsystems/pty.md +pty.md: 45b157c73926d85d5fc2bb037d3954da7bf75063 +pty.zh.md: 4409ef0b0504780b0b5564a34b61d4f1b2c02775 diff --git a/docs/subsystems/pty.md b/docs/subsystems/pty.md index 97e1e662d1..45b157c739 100644 --- a/docs/subsystems/pty.md +++ b/docs/subsystems/pty.md @@ -89,3 +89,96 @@ interface PtySendResult { ## Ownership and durability `PtyService` attaches one awaited cleanup to the exact owner scope, rejects foreign operations, and keeps sessions alive across backend or tool-plugin reload. PTY state and raw bytes remain process-local. Model input and bounded returned output are durable through the existing `tool/call`, `tool/result`, and task-result paths rather than duplicate PTY session events. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.pty` — `PtyService` + +In-process registry for replaceable PTY backends and exact-Agent sessions. + +```ts cordis-catalog +/** + * Register one backend type for this effect scope. + * @param backend - provider with a non-empty unique type. + * @returns disposer that removes exactly this contribution. + */ +registerBackend(backend: PtyBackend): () => void + +/** + * List registered backend types in registration order. + * @returns fresh backend type names. + */ +listBackends(): string[] + +/** + * Create and publish one owner-scoped session after backend setup succeeds. + * @param owner - exact registered Agent that owns access and cleanup. + * @param request - backend type plus optional owner-local name and cwd. + * @param signal - cancellation of unpublished setup. + * @returns published identity, metadata, status, and MOTD. + */ +async spawn(owner: Agent, request: PtySpawnRequest, signal?: AbortSignal): Promise + +/** + * Test whether an exact owner has a published session or unpublished spawn. + * @param owner - exact live owner to inspect. + * @returns true across the entire spawn-to-close interval, with no publication gap. + */ +hasOwnerActivity(owner: Agent): boolean + +/** + * Start one exclusive interactive send. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param request - explicit text, submit behavior, and cancellation. + * @returns live operation handle for foreground await or task registration. + */ +startSend(owner: Agent, id: PtySessionId, request: PtySendRequest): PtySendOperation + +/** + * Read one bounded scrollback page from an owned session. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param request - optional newest-relative offset and line count. + * @returns bounded retained text and pagination metadata. + */ +read(owner: Agent, id: PtySessionId, request: PtyReadRequest = {}): PtyReadResult + +/** + * Deliver an allowed signal through an owned backend session. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param signal - allowed POSIX signal name. + * @returns delivered foreground process-group identity. + */ +signal(owner: Agent, id: PtySessionId, signal: PtySignal): Promise + +/** + * Close one owned session and remove it only after quiescent backend cleanup. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param reason - diagnostic cleanup reason. + * @returns true for a newly closed session, false when the same close is already in flight. + */ +async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise + +/** + * List fresh snapshots for exactly one owner. + * @param owner - exact owner whose sessions are visible. + * @returns owner-visible snapshots in publication order. + */ +list(owner: Agent): PtySessionSnapshot[] +``` + +Types: [Agent](core.md) + +Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts) + diff --git a/docs/subsystems/pty.zh.md b/docs/subsystems/pty.zh.md index a57f744827..4409ef0b05 100644 --- a/docs/subsystems/pty.zh.md +++ b/docs/subsystems/pty.zh.md @@ -89,3 +89,96 @@ interface PtySendResult { ## 归属与持久性 `PtyService` 会将一项等待完成的清理附加到确切的拥有者作用域,拒绝其他拥有者的操作,并让会话在后端或工具插件重载期间保持存活。PTY 状态与原始字节仍局限在进程内。模型输入与有界返回输出通过现有 `tool/call`、`tool/result` 和任务结果路径持久保存,而不是重复记录 PTY 会话事件。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.pty` — `PtyService` + +In-process registry for replaceable PTY backends and exact-Agent sessions. + +```ts cordis-catalog +/** + * Register one backend type for this effect scope. + * @param backend - provider with a non-empty unique type. + * @returns disposer that removes exactly this contribution. + */ +registerBackend(backend: PtyBackend): () => void + +/** + * List registered backend types in registration order. + * @returns fresh backend type names. + */ +listBackends(): string[] + +/** + * Create and publish one owner-scoped session after backend setup succeeds. + * @param owner - exact registered Agent that owns access and cleanup. + * @param request - backend type plus optional owner-local name and cwd. + * @param signal - cancellation of unpublished setup. + * @returns published identity, metadata, status, and MOTD. + */ +async spawn(owner: Agent, request: PtySpawnRequest, signal?: AbortSignal): Promise + +/** + * Test whether an exact owner has a published session or unpublished spawn. + * @param owner - exact live owner to inspect. + * @returns true across the entire spawn-to-close interval, with no publication gap. + */ +hasOwnerActivity(owner: Agent): boolean + +/** + * Start one exclusive interactive send. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param request - explicit text, submit behavior, and cancellation. + * @returns live operation handle for foreground await or task registration. + */ +startSend(owner: Agent, id: PtySessionId, request: PtySendRequest): PtySendOperation + +/** + * Read one bounded scrollback page from an owned session. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param request - optional newest-relative offset and line count. + * @returns bounded retained text and pagination metadata. + */ +read(owner: Agent, id: PtySessionId, request: PtyReadRequest = {}): PtyReadResult + +/** + * Deliver an allowed signal through an owned backend session. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param signal - allowed POSIX signal name. + * @returns delivered foreground process-group identity. + */ +signal(owner: Agent, id: PtySessionId, signal: PtySignal): Promise + +/** + * Close one owned session and remove it only after quiescent backend cleanup. + * @param owner - exact session owner. + * @param id - target PTY identity. + * @param reason - diagnostic cleanup reason. + * @returns true for a newly closed session, false when the same close is already in flight. + */ +async kill(owner: Agent, id: PtySessionId, reason: string = 'model request'): Promise + +/** + * List fresh snapshots for exactly one owner. + * @param owner - exact owner whose sessions are visible. + * @returns owner-visible snapshots in publication order. + */ +list(owner: Agent): PtySessionSnapshot[] +``` + +Types: [Agent](core.md) + +Source: [`packages/pty/pty/src/index.ts:105`](../../packages/pty/pty/src/index.ts) + diff --git a/docs/subsystems/sandbox.i18n.yaml b/docs/subsystems/sandbox.i18n.yaml index 34691ad25b..efbd158e18 100644 --- a/docs/subsystems/sandbox.i18n.yaml +++ b/docs/subsystems/sandbox.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/sandbox.md -sandbox.md: 9e5feafe046f18dad49aeaf281793f0b8e03c240 -sandbox.zh.md: a1314d1b78eb0d46ea4c8ca5aa330ee83bf89132 +# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md +sandbox.md: dd960b3021dcdc87cfd36fd439cbec0a810dd736 +sandbox.zh.md: f526982a782144a0bf58abcde5d883e9dc813369 diff --git a/docs/subsystems/sandbox.md b/docs/subsystems/sandbox.md index 9e5feafe04..dd960b3021 100644 --- a/docs/subsystems/sandbox.md +++ b/docs/subsystems/sandbox.md @@ -146,3 +146,65 @@ The [local provider](../../packages/sandbox/sandbox-local/README.md) owns operat `ctx.sandbox.confine(argv, policy)` returns a `ConfinedArgv` or throws `SandboxUnavailableError` with code `SANDBOX_UNAVAILABLE` when no usable backend exists. Consumers may also classify a failure while spawning or observing the returned argv; that attribution belongs to the consumer contract. Silent unconfined passthrough is never legal for a confined policy. Provider selection, probing, caching, and backend-specific enforcement reports belong to the [local provider](../../packages/sandbox/sandbox-local/README.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sandbox` — `SandboxProvider` (abstract seam) + +Abstract process-sandbox service. confine must return enforcing argv or fail closed at wrap or runner-execution time; silent unconfined passthrough is forbidden. Functional probes arbitrate multi-runner chains and may be skipped for a sole candidate, whose own refusal remains the fail-closed end. + +```ts cordis-catalog +/** + * Wrap `argv` so it executes confined under `policy` on this host; the + * caller spawns the returned argv in place of its own. + * @param argv - the exact argv the caller is about to spawn (program plus + * arguments), NOT a shell string — a shell-shaped consumer passes + * `['bash', '-c', command]`. + * @param policy - the file-effect policy this execution runs under, + * carried per call (see {@link SandboxPolicy}). + * @returns the argv to spawn instead, plus the enforcement completeness + * the selected backend achieves for it. + */ +abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv +``` + +Source: [`packages/sandbox/sandbox/src/index.ts:148`](../../packages/sandbox/sandbox/src/index.ts) + + + +### `ctx.sandboxPolicy` — `SandboxPolicyService` + +The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. + +```ts cordis-catalog +/** + * Resolve the complete policy for one capability call. An approved explicit + * mode outranks the session's last `sandbox/mode` event, which outranks the + * deployment default. A session cwd is its workspace-write boundary; the + * configured root is the fallback for agentless calls and sessions without a + * cwd. + * @param request - optional session and approved mode override. + * @returns the fully resolved per-call mode and absolute workspace root. + */ +resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy + +/** + * Read the session override without applying the deployment default. + * @param session - session whose log supplies the override. + * @returns the last logged mode, or `undefined` without one. + */ +overrideOf(session: Session): SandboxMode | undefined +``` + +Types: [Session](session.md) + +Source: [`packages/sandbox/sandbox-policy/src/index.ts:91`](../../packages/sandbox/sandbox-policy/src/index.ts) + diff --git a/docs/subsystems/sandbox.zh.md b/docs/subsystems/sandbox.zh.md index a1314d1b78..f526982a78 100644 --- a/docs/subsystems/sandbox.zh.md +++ b/docs/subsystems/sandbox.zh.md @@ -146,3 +146,65 @@ interface ConfinedArgv { `ctx.sandbox.confine(argv, policy)` 返回一个 `ConfinedArgv`,或在没有可用后端时抛出 `SandboxUnavailableError`(错误码 `SANDBOX_UNAVAILABLE`)。消费方也可以在 spawn 或观察所返回的 argv 时对失败进行分类;该归因属于消费方契约。对于受限策略,静默的无隔离透传永远不合法。 提供方选择、探测、缓存和后端专有的强制执行报告归[本地提供方](../../packages/sandbox/sandbox-local/README.md)所有。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sandbox` — `SandboxProvider` (abstract seam) + +Abstract process-sandbox service. confine must return enforcing argv or fail closed at wrap or runner-execution time; silent unconfined passthrough is forbidden. Functional probes arbitrate multi-runner chains and may be skipped for a sole candidate, whose own refusal remains the fail-closed end. + +```ts cordis-catalog +/** + * Wrap `argv` so it executes confined under `policy` on this host; the + * caller spawns the returned argv in place of its own. + * @param argv - the exact argv the caller is about to spawn (program plus + * arguments), NOT a shell string — a shell-shaped consumer passes + * `['bash', '-c', command]`. + * @param policy - the file-effect policy this execution runs under, + * carried per call (see {@link SandboxPolicy}). + * @returns the argv to spawn instead, plus the enforcement completeness + * the selected backend achieves for it. + */ +abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv +``` + +Source: [`packages/sandbox/sandbox/src/index.ts:148`](../../packages/sandbox/sandbox/src/index.ts) + + + +### `ctx.sandboxPolicy` — `SandboxPolicyService` + +The sandbox-policy service (`ctx.sandboxPolicy`). Owns the deployment default mode, fallback workspace root, and current request-time policy section. Tool layers call resolve for each execution so a session's mode log and immutable cwd travel together to every enforcing capability. + +```ts cordis-catalog +/** + * Resolve the complete policy for one capability call. An approved explicit + * mode outranks the session's last `sandbox/mode` event, which outranks the + * deployment default. A session cwd is its workspace-write boundary; the + * configured root is the fallback for agentless calls and sessions without a + * cwd. + * @param request - optional session and approved mode override. + * @returns the fully resolved per-call mode and absolute workspace root. + */ +resolve(request: SandboxPolicyRequest = {}): SandboxExecutionPolicy + +/** + * Read the session override without applying the deployment default. + * @param session - session whose log supplies the override. + * @returns the last logged mode, or `undefined` without one. + */ +overrideOf(session: Session): SandboxMode | undefined +``` + +Types: [Session](session.md) + +Source: [`packages/sandbox/sandbox-policy/src/index.ts:91`](../../packages/sandbox/sandbox-policy/src/index.ts) + diff --git a/docs/subsystems/session-projection.i18n.yaml b/docs/subsystems/session-projection.i18n.yaml new file mode 100644 index 0000000000..24b1c7b234 --- /dev/null +++ b/docs/subsystems/session-projection.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md +session-projection.md: 56f9fb43dddc482264aab52ceeb9b9920df148cd +session-projection.zh.md: 4a2310fed4af6f018af07b5f2b46d3c28aa361a4 diff --git a/docs/subsystems/session-projection.md b/docs/subsystems/session-projection.md new file mode 100644 index 0000000000..56f9fb43dd --- /dev/null +++ b/docs/subsystems/session-projection.md @@ -0,0 +1,262 @@ +# Session Projections + +English | [中文](session-projection.zh.md) + +The session-projection seam — a [capability seam](../capability-seams.md) through which domain host plugins serve whole current values of log-derived per-session state to client carriers: the interface and registry ([dsh-session-projection](../../packages/session/session-projection), `ctx.sessionProjections`), domain contributors (each registering one pure unit), and carriers ([dsh-host-apiproxy](../../packages/host/apiproxy)'s history tail page and `session/projection` push frame today). It is one optional capability, not part of the agent-loop spine. The framework drives, the domain computes: the registry subscribes to `session/event` once and folds every committed event through every unit; domains hold no subscriptions and clients never fold domain events — they receive finished values. Design authority: the [session-projection RFC](../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md); drive/cache/feed contracts: the [package README](../../packages/session/session-projection/README.md). + +Source: [`packages/session/session-projection/src/index.ts`](../../packages/session/session-projection/src/index.ts) + +## The unit + +`SessionProjectionMap` is the merge-extensible type table for the whole chain (host unit, wire block, client hook); values are wire-JSON whole values, and rendering belongs to the slot system, never this layer. A domain contributes one `ProjectionDefinition` per key: + +```ts type-equiv +/** + * One domain's state-driven computation unit: three pure synchronous + * functions plus declarations — never an opaque getter. The framework drives + * `apply` on every committed session event; the domain holds no + * subscriptions and owns only the mathematics. All three functions MUST be + * synchronous (an async unit would tear the carriers' consistency cut) and + * `state` MUST be plain JSON (the persisted-cache precondition). + */ +interface ProjectionDefinition { + /** The projection key this unit owns (its `SessionProjectionMap` entry). */ + key: K + /** Validates the wire payload (`view` output) before it leaves the host. */ + schema: ZodType + /** + * State for the empty log. + * @returns the initial state. + */ + init(): S + /** + * Pure transition: previous state + one committed event → next state. A + * unit uninterested in an event MUST return the same state reference — an + * unchanged reference (`Object.is`) produces zero downstream work. + * @param state - the state covering all prior events. + * @param event - the next committed session event. + * @returns the next state (same reference when the event is not the unit's). + */ + apply(state: S, event: SessionEvent): S + /** + * State → wire payload (the read-side projection). + * @param state - the current state. + * @returns the whole current value for this unit's key. + */ + view(state: S): SessionProjectionMap[K] + /** + * Persisted-cache invalidation anchor: bump whenever the state shape or the + * fold semantics change, so persisted `(sessionId, key, ver, seq, val)` + * rows from an older unit are discarded instead of being forward-applied + * into garbage. Non-negative integer. + */ + stateVersion: number +} +``` + +The whole-value event rule is load-bearing: a state-carrying log event carries the complete post-change state, never a bare delta — it keeps every transition trivially cheap and every served value self-describing (last-wins for consumers). + +## The snapshot and the change feed + +```ts type-equiv +/** + * One consistent read cut over every registered unit for one session. + * `asOfSeq` is the shared watermark — the seq of the last event every value + * reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`). + */ +interface ProjectionSnapshot { + /** Seq of the last event the values reflect; -1 for an empty log. */ + asOfSeq: number + /** Whole current value per registered key. */ + values: Partial +} +``` + +```ts type-equiv +/** + * Change-feed listener: one unit's value changed for one session. `value` is + * the schema-validated `view` output; `seq` is the unit's watermark at + * emission (the seq of the event that caused the change). + */ +type ProjectionChangeListener = ( + session: Session, + key: Extract, + value: unknown, + seq: number, +) => void +``` + +`snapshot(session)` is fully synchronous — a carrier reads it in the same tick as its page slice, which is what makes `asOfSeq` one consistent cut — and every value passes its unit's schema before leaving (an accidentally-async `view` returns a Promise, which fails that boundary parse loudly). The change feed fires once per unit whose state *reference* changed, per committed event: the same-reference discipline in `apply` is the gate. + +## The registry: `ctx.sessionProjections` + +`SessionProjectionRegistry` ([signatures](#ctxsessionprojections--sessionprojectionregistry)) owns the drive: one `session/event` subscription, eager `apply` over every registered unit, and per-session per-unit watermark cells. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect whose disposer rides the calling fiber: an unloaded domain plugin's key (with its cached cells) disappears from subsequent drives and snapshots, and clients read that as capability absence; duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionProjectionCache` — `SessionProjectionCache` + +The persisted projection cache service. Opens the `session_projcache` domain at init, checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached row, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. + +```ts cordis-catalog +/** + * The zero-I/O listing read: whole values viewed straight from the stored + * rows (version-matching keys only), each cut carried with its watermark + * so a client value store can seed under its higher-seq-wins rule — as + * stale as the last durable checkpoint but never wrong, and never from an + * unrelated log (the caller's header is the identity witness). Fresher + * paths (the history tail baseline, {@link coldSnapshot}) supersede these + * values whenever a session is actually opened. + * @param meta - the listed session's header (identity witness; no log read). + * @returns the cut (`asOfSeq` = lowest served-row watermark), or + * `undefined` when no usable row exists for this lifecycle. + */ +cachedSnapshot(meta: SessionHeader): ProjectionSnapshot | undefined + +/** + * Durably checkpoint one live session NOW (both mandatory points call + * this; tests and carriers may too). The registry cut is snapshotted at + * this boundary (states are live references), then the whole record is + * replaced. NOT fail-soft — callers on the fail-soft paths contain it. + * @param session - the live session to checkpoint. + * @returns resolution after durability and event emission. + */ +async write(session: Session): Promise + +/** + * Cold-read one persisted session's projections with zero full-log load: + * cached rows + a persistence `readFrom` tail from the registry's restore + * floor, refolded by the registry and written back (fail-soft) so the next + * cold read starts closer. A cache row invalidated by a shrunk log + * (crash-repair truncation) triggers one full re-read from seq 0 — the + * ladder's slow rung, still no crash. Rejects when the session has no + * persisted log (`not found` from the persistence seam). + * @param id - the persisted session to read. + * @param signal - optional cancellation for the persistence reads. + * @returns the snapshot cut at the stored log end. + */ +async coldSnapshot(id: SessionId, signal?: AbortSignal): Promise +``` + +Types: [Session](session.md) · [SessionHeader](persistence.md) · [SessionId](core.md) + +Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../packages/session/session-projection-cache/src/index.ts) + + + +### `ctx.sessionProjections` — `SessionProjectionRegistry` + +`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. + +```ts cordis-catalog +/** + * Register one domain's unit. The registration is an effect on the calling + * context's fiber: disposing the fiber (or calling the returned disposer) + * removes the key — and the unit's cached cells — from subsequent drives + * and snapshots. + * @param definition - key, boundary schema, pure unit functions, and stateVersion. + * @returns the exact disposer that unregisters this unit. + */ +register(definition: ProjectionDefinition): () => void + +/** + * Subscribe to the change feed. The registration is an effect on the + * calling context's fiber. + * @param listener - called once per unit whose state reference changed, per committed event. + * @returns the exact disposer that unsubscribes. + */ +onChanged(listener: ProjectionChangeListener): () => void + +/** + * One consistent cut over every registered unit for one session, read from + * the watermark cache (missing cells fold lazily over the in-memory log). + * Fully synchronous — every value and `asOfSeq` reflect the same log + * position. Each value passes its unit's schema before leaving. + * @param session - the session whose projection values are read. + * @returns the snapshot; `values` is empty when no unit is registered. + */ +snapshot(session: Session): ProjectionSnapshot + +/** + * State-level checkpoint of every registered unit for one session, read + * from the watermark cache (missing cells fold lazily over the in-memory + * log). This is the write side of the persisted projection cache: the + * returned rows are the `(key → {ver, seq, val})` part of the durable + * `(sessionId, key, ver, seq, val)` + * rows. Every `val` is a DETACHED structured clone — never the live + * cell reference: the watermark cache is this registry's authoritative + * mutable state, and a caller reaching the live reference could corrupt + * every subsequent snapshot and frame through it (plain JSON by the unit + * contract, so the clone is total). + * @param session - the session whose unit states are checkpointed. + * @returns one row per registered key; empty when no unit is registered. + */ +checkpoint(session: Session): ProjectionCheckpoint + +/** + * The stored seq a {@link restore} tail read over `checkpoint` must start + * at: one event BELOW the lowest usable watermark (a row is usable when + * its `ver` matches the live unit's `stateVersion`; an absent or mismatched row + * pulls the floor to `0` — that key must refold the full log). The + * one-below anchor is load-bearing: the tail then proves how far the + * stored log still extends, so {@link restore} can detect a log that + * shrank below a row's watermark (crash-repair truncation) instead of + * serving the stale row as current — an empty tail read from the anchor + * yields an end below every watermark and the restore rejects for a full + * re-read. + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @returns the seq to hand the persistence `readFrom`, or `undefined` + * when no unit is registered (no read needed — {@link restore} would + * serve empty values regardless). + */ +restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined + +/** + * View a checkpoint's rows without any log read: for every registered + * unit whose row's `ver` matches, serve the schema-validated + * `view` of the stored state; mismatched or absent rows leave their key + * absent (a cold or listing consumer treats it as not-yet-available and a + * fuller read path refolds it). The zero-I/O rung of the read ladder — + * values are as stale as their rows, never wrong. + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @returns whole values per key with a usable row; empty when none. + */ +viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial + +/** + * Cold read: fold every registered unit over a stored log suffix, seeding + * each from its checkpoint row when usable — the one read recipe (cached + * state + forward tail replay + `view`) applied without a live `Session`. + * Call with the events returned by a persistence + * `readFrom(id, restoreFloor(checkpoint))` and that same floor as + * `baseSeq`; the floor's one-below anchor makes the supplied end honest, + * so a shrunk log is detected here. A row is usable iff its + * `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq` + * (`seq >= baseSeq - 1`), and it does not claim events past the + * supplied end (`seq <= endSeq`); an unusable row is discarded + * and its key refolds from `init` — which is only sound over the full + * log, so a discarded row with `baseSeq > 0` throws (the caller re-reads + * from seq 0, e.g. after a crash-repair truncation shrank the log below + * a row's watermark). + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @param events - the stored events with `seq >= baseSeq`, in seq order. + * @param baseSeq - the seq `events` starts at (its first event's seq when non-empty). + * @returns the snapshot cut at the supplied log end (`asOfSeq` is the last + * supplied event's seq, `baseSeq - 1` for an empty tail) plus the + * refreshed checkpoint rows at that cut, ready for a durable write-back. + */ +restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } +``` + +Types: [Session](session.md) · [SessionEvent](core.md) + +Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) + diff --git a/docs/subsystems/session-projection.zh.md b/docs/subsystems/session-projection.zh.md new file mode 100644 index 0000000000..4a2310fed4 --- /dev/null +++ b/docs/subsystems/session-projection.zh.md @@ -0,0 +1,262 @@ +# 会话投影 + +[English](session-projection.md) | 中文 + +会话投影 seam 是一项[能力 seam](../capability-seams.md):领域 host 插件经由它向客户端载体供给按会话的日志派生状态的当前全量值;三方分别是接口与注册表([dsh-session-projection](../../packages/session/session-projection),`ctx.sessionProjections`)、领域贡献方(每个领域注册一个纯单元)与载体(今天是 [dsh-host-apiproxy](../../packages/host/apiproxy) 的历史尾页与 `session/projection` 推送帧)。它是一项可选能力,不属于 agent loop(智能体循环)主干。框架负责驱动,领域负责计算:注册表只订阅一次 `session/event`,并把每个已提交事件折叠进每个单元;领域不持有任何订阅,客户端也从不折叠领域事件——它们收到的是成品值。设计权威:[session-projection RFC](../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md);驱动、缓存与变更流契约:[包(package)README](../../packages/session/session-projection/README.md)。 + +源码:[`packages/session/session-projection/src/index.ts`](../../packages/session/session-projection/src/index.ts) + +## 投影单元 + +`SessionProjectionMap` 是整条链路(host 侧单元、协议块、客户端钩子)的 merge-extensible 类型表;值是协议层 JSON 全量值,渲染归 slot 体系管,永远不归本层。领域为每个 key 贡献一个 `ProjectionDefinition`: + +```ts type-equiv +/** + * One domain's state-driven computation unit: three pure synchronous + * functions plus declarations — never an opaque getter. The framework drives + * `apply` on every committed session event; the domain holds no + * subscriptions and owns only the mathematics. All three functions MUST be + * synchronous (an async unit would tear the carriers' consistency cut) and + * `state` MUST be plain JSON (the persisted-cache precondition). + */ +interface ProjectionDefinition { + /** The projection key this unit owns (its `SessionProjectionMap` entry). */ + key: K + /** Validates the wire payload (`view` output) before it leaves the host. */ + schema: ZodType + /** + * State for the empty log. + * @returns the initial state. + */ + init(): S + /** + * Pure transition: previous state + one committed event → next state. A + * unit uninterested in an event MUST return the same state reference — an + * unchanged reference (`Object.is`) produces zero downstream work. + * @param state - the state covering all prior events. + * @param event - the next committed session event. + * @returns the next state (same reference when the event is not the unit's). + */ + apply(state: S, event: SessionEvent): S + /** + * State → wire payload (the read-side projection). + * @param state - the current state. + * @returns the whole current value for this unit's key. + */ + view(state: S): SessionProjectionMap[K] + /** + * Persisted-cache invalidation anchor: bump whenever the state shape or the + * fold semantics change, so persisted `(sessionId, key, ver, seq, val)` + * rows from an older unit are discarded instead of being forward-applied + * into garbage. Non-negative integer. + */ + stateVersion: number +} +``` + +全量值事件规则是承重结构:携带状态的日志事件携带的是变更后的完整状态,绝不是裸增量——这让每次状态转移始终足够廉价,也让每个被供给的值自描述(对消费方即 last-wins)。 + +## 快照与变更流 + +```ts type-equiv +/** + * One consistent read cut over every registered unit for one session. + * `asOfSeq` is the shared watermark — the seq of the last event every value + * reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`). + */ +interface ProjectionSnapshot { + /** Seq of the last event the values reflect; -1 for an empty log. */ + asOfSeq: number + /** Whole current value per registered key. */ + values: Partial +} +``` + +```ts type-equiv +/** + * Change-feed listener: one unit's value changed for one session. `value` is + * the schema-validated `view` output; `seq` is the unit's watermark at + * emission (the seq of the event that caused the change). + */ +type ProjectionChangeListener = ( + session: Session, + key: Extract, + value: unknown, + seq: number, +) => void +``` + +`snapshot(session)` 是完全同步的:载体在切出页面切片的同一 tick 内读取它,`asOfSeq` 之所以是一个一致切面正系于此;且每个值在离开前都要经过其单元的 schema 校验(误写成异步的 `view` 会返回 Promise,让这道边界解析当场大声失败)。变更流对每个已提交事件、每个状态*引用*发生变化的单元各触发一次:`apply` 的同引用纪律就是那道闸门。 + +## 注册表:`ctx.sessionProjections` + +`SessionProjectionRegistry`([签名](#ctxsessionprojections--sessionprojectionregistry))拥有驱动权:一份 `session/event` 订阅、对每个已注册单元的正向 `apply`,以及每会话每单元的水位线(watermark)cell。cell 惰性构建:在事件流过之后才注册的单元,或比注册表更早的会话,都在首次触达(事件或读取)时从 `init` 出发在内存日志上折叠。注册是一个 effect,其 disposer 随调用方 fiber 走:领域插件卸载后,其 key(连同缓存的 cell)从后续驱动与快照中消失,客户端将其读作能力缺失;key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionProjectionCache` — `SessionProjectionCache` + +The persisted projection cache service. Opens the `session_projcache` domain at init, checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached row, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. + +```ts cordis-catalog +/** + * The zero-I/O listing read: whole values viewed straight from the stored + * rows (version-matching keys only), each cut carried with its watermark + * so a client value store can seed under its higher-seq-wins rule — as + * stale as the last durable checkpoint but never wrong, and never from an + * unrelated log (the caller's header is the identity witness). Fresher + * paths (the history tail baseline, {@link coldSnapshot}) supersede these + * values whenever a session is actually opened. + * @param meta - the listed session's header (identity witness; no log read). + * @returns the cut (`asOfSeq` = lowest served-row watermark), or + * `undefined` when no usable row exists for this lifecycle. + */ +cachedSnapshot(meta: SessionHeader): ProjectionSnapshot | undefined + +/** + * Durably checkpoint one live session NOW (both mandatory points call + * this; tests and carriers may too). The registry cut is snapshotted at + * this boundary (states are live references), then the whole record is + * replaced. NOT fail-soft — callers on the fail-soft paths contain it. + * @param session - the live session to checkpoint. + * @returns resolution after durability and event emission. + */ +async write(session: Session): Promise + +/** + * Cold-read one persisted session's projections with zero full-log load: + * cached rows + a persistence `readFrom` tail from the registry's restore + * floor, refolded by the registry and written back (fail-soft) so the next + * cold read starts closer. A cache row invalidated by a shrunk log + * (crash-repair truncation) triggers one full re-read from seq 0 — the + * ladder's slow rung, still no crash. Rejects when the session has no + * persisted log (`not found` from the persistence seam). + * @param id - the persisted session to read. + * @param signal - optional cancellation for the persistence reads. + * @returns the snapshot cut at the stored log end. + */ +async coldSnapshot(id: SessionId, signal?: AbortSignal): Promise +``` + +Types: [Session](session.md) · [SessionHeader](persistence.md) · [SessionId](core.md) + +Source: [`packages/session/session-projection-cache/src/index.ts:71`](../../packages/session/session-projection-cache/src/index.ts) + + + +### `ctx.sessionProjections` — `SessionProjectionRegistry` + +`ctx.sessionProjections`: the projection unit table and its drive. The service subscribes to `session/event` once; every committed event passes every registered unit's `apply` (eager drive), and a changed state reference notifies the change feed with the schema-validated view. Cells build lazily — a unit registered after events flowed, or a session older than the registry, folds `init` over the in-memory log on first touch (event or read). Registration is an effect (disposer rides the calling fiber): an unloaded domain plugin's key disappears from snapshots and clients read it as capability absence. Duplicate keys throw. Domain plugins register under `ctx.inject(['sessionProjections'], …)` so headless assemblies without the registry stay unaffected. + +```ts cordis-catalog +/** + * Register one domain's unit. The registration is an effect on the calling + * context's fiber: disposing the fiber (or calling the returned disposer) + * removes the key — and the unit's cached cells — from subsequent drives + * and snapshots. + * @param definition - key, boundary schema, pure unit functions, and stateVersion. + * @returns the exact disposer that unregisters this unit. + */ +register(definition: ProjectionDefinition): () => void + +/** + * Subscribe to the change feed. The registration is an effect on the + * calling context's fiber. + * @param listener - called once per unit whose state reference changed, per committed event. + * @returns the exact disposer that unsubscribes. + */ +onChanged(listener: ProjectionChangeListener): () => void + +/** + * One consistent cut over every registered unit for one session, read from + * the watermark cache (missing cells fold lazily over the in-memory log). + * Fully synchronous — every value and `asOfSeq` reflect the same log + * position. Each value passes its unit's schema before leaving. + * @param session - the session whose projection values are read. + * @returns the snapshot; `values` is empty when no unit is registered. + */ +snapshot(session: Session): ProjectionSnapshot + +/** + * State-level checkpoint of every registered unit for one session, read + * from the watermark cache (missing cells fold lazily over the in-memory + * log). This is the write side of the persisted projection cache: the + * returned rows are the `(key → {ver, seq, val})` part of the durable + * `(sessionId, key, ver, seq, val)` + * rows. Every `val` is a DETACHED structured clone — never the live + * cell reference: the watermark cache is this registry's authoritative + * mutable state, and a caller reaching the live reference could corrupt + * every subsequent snapshot and frame through it (plain JSON by the unit + * contract, so the clone is total). + * @param session - the session whose unit states are checkpointed. + * @returns one row per registered key; empty when no unit is registered. + */ +checkpoint(session: Session): ProjectionCheckpoint + +/** + * The stored seq a {@link restore} tail read over `checkpoint` must start + * at: one event BELOW the lowest usable watermark (a row is usable when + * its `ver` matches the live unit's `stateVersion`; an absent or mismatched row + * pulls the floor to `0` — that key must refold the full log). The + * one-below anchor is load-bearing: the tail then proves how far the + * stored log still extends, so {@link restore} can detect a log that + * shrank below a row's watermark (crash-repair truncation) instead of + * serving the stale row as current — an empty tail read from the anchor + * yields an end below every watermark and the restore rejects for a full + * re-read. + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @returns the seq to hand the persistence `readFrom`, or `undefined` + * when no unit is registered (no read needed — {@link restore} would + * serve empty values regardless). + */ +restoreFloor(checkpoint: ProjectionCheckpoint): number | undefined + +/** + * View a checkpoint's rows without any log read: for every registered + * unit whose row's `ver` matches, serve the schema-validated + * `view` of the stored state; mismatched or absent rows leave their key + * absent (a cold or listing consumer treats it as not-yet-available and a + * fuller read path refolds it). The zero-I/O rung of the read ladder — + * values are as stale as their rows, never wrong. + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @returns whole values per key with a usable row; empty when none. + */ +viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial + +/** + * Cold read: fold every registered unit over a stored log suffix, seeding + * each from its checkpoint row when usable — the one read recipe (cached + * state + forward tail replay + `view`) applied without a live `Session`. + * Call with the events returned by a persistence + * `readFrom(id, restoreFloor(checkpoint))` and that same floor as + * `baseSeq`; the floor's one-below anchor makes the supplied end honest, + * so a shrunk log is detected here. A row is usable iff its + * `ver` matches the live unit's `stateVersion`, it does not predate `baseSeq` + * (`seq >= baseSeq - 1`), and it does not claim events past the + * supplied end (`seq <= endSeq`); an unusable row is discarded + * and its key refolds from `init` — which is only sound over the full + * log, so a discarded row with `baseSeq > 0` throws (the caller re-reads + * from seq 0, e.g. after a crash-repair truncation shrank the log below + * a row's watermark). + * @param checkpoint - persisted rows for one session (possibly stale or empty). + * @param events - the stored events with `seq >= baseSeq`, in seq order. + * @param baseSeq - the seq `events` starts at (its first event's seq when non-empty). + * @returns the snapshot cut at the supplied log end (`asOfSeq` is the last + * supplied event's seq, `baseSeq - 1` for an empty tail) plus the + * refreshed checkpoint rows at that cut, ready for a durable write-back. + */ +restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } +``` + +Types: [Session](session.md) · [SessionEvent](core.md) + +Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) + diff --git a/docs/subsystems/session-query.i18n.yaml b/docs/subsystems/session-query.i18n.yaml index eca4715ba9..728a6cf8db 100644 --- a/docs/subsystems/session-query.i18n.yaml +++ b/docs/subsystems/session-query.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/session-query.md -session-query.md: e7514dd6c3bc20a07395663bff40ce65e1363b78 -session-query.zh.md: 4c3dd4d435dbd8a20fbd4db5da1a7d649c2e6d0b +# pnpm run verify-translation-pairing --write docs/subsystems/session-query.md +session-query.md: 54ae1eaf34a17d4979a621e4fc310780b425364e +session-query.zh.md: b770514757c30aa5d87b25e5d280b1ce6710cd9c diff --git a/docs/subsystems/session-query.md b/docs/subsystems/session-query.md index e7514dd6c3..54ae1eaf34 100644 --- a/docs/subsystems/session-query.md +++ b/docs/subsystems/session-query.md @@ -354,3 +354,141 @@ type SessionQueryErrorCode = | 'SESSION_QUERY_STALE_CURSOR' | 'SESSION_QUERY_SOURCE_CONFLICT' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionQuery` — `SessionQueryService` (abstract seam) + +Unified live-preferred session query service. + +Exact reads, filters, and traces are backend-independent concrete behavior. A backend implements full-text observation, reconciliation, ranking, cursor generations, and query execution on the same `ctx.sessionQuery` service. + +```ts cordis-catalog +/** + * Search the live-preferred logical corpus and group by session. + * @param request - query text, metadata filters, page size, and cursor. + * @param exec - optional cancellation control. + * @returns session hits ranked by their strongest matching event. + */ +abstract searchSessions( request: SessionSearchRequest, exec?: SessionSearchExecContext, ): Promise> + +/** + * Search events within one live-preferred logical session. + * @param request - target session, query text, filters, page size, and cursor. + * @param exec - optional cancellation control. + * @returns matching event hits and their target header from one indexed generation. + */ +abstract searchEvents( request: SessionEventSearchRequest, exec?: SessionSearchExecContext, ): Promise + +/** + * List the complete logical corpus using live-preferred records. + * @param signal - optional cancellation for persistence listing. + * @returns deterministic newest-first cloned session records. + */ +listSessions(signal?: AbortSignal): Promise + +/** + * Read and replay-validate one complete logical session log without making it live. + * @param sessionId - live or persisted session id to read. + * @returns cloned header and complete raw event log from one observation. + * @throws when persistence, header compatibility, or replay validation fails. + */ +async readSession(sessionId: SessionId): Promise + +/** + * Filter the complete logical corpus with provider-independent predicates. + * @param filters - ANDed session metadata and availability clauses. + * @param signal - optional cancellation for persistence listing. + * @returns matching cloned records in deterministic newest-first order. + */ +async filterSessions( filters: readonly SessionResultFilter[], signal?: AbortSignal, ): Promise + +/** + * Fold the latest log-backed title from one live-preferred logical session. + * @param sessionId - live or persisted session id to read. + * @param signal - optional cancellation for source resolution and title folding. + * @returns latest title snapshot, or `undefined` when the log has no title event. + */ +async readTitle( sessionId: SessionId, signal?: AbortSignal, ): Promise + +/** + * Fold the latest title and return its source header from one corpus observation. + * @param sessionId - live or persisted session id to read. + * @param signal - optional cancellation for source resolution and title folding. + * @returns cloned source header and optional latest title snapshot. + */ +async readTitleSnapshot( sessionId: SessionId, signal?: AbortSignal, ): Promise + +/** + * Fold titles for unique sessions from one cancellable corpus observation. + * + * Results preserve first-occurrence input order. Operational failures stay + * isolated per session, while cancellation rejects the complete operation. + * @param sessionIds - live or persisted session ids to observe. + * @param signal - optional cancellation shared by all source reads. + * @returns one fulfilled or rejected result per unique requested id. + */ +async readTitleSnapshots( sessionIds: readonly SessionId[], signal?: AbortSignal, ): Promise + +/** + * List lightweight raw-log event records for one logical session. + * @param sessionId - live-preferred session id to read. + * @returns event records in ascending seq order. + */ +async listEvents(sessionId: SessionId): Promise + +/** + * Scan first-party semantic event documents with provider-independent filters. + * @param sessionId - live-preferred session id to scan. + * @param filters - ANDed metadata and literal-text predicates. + * @returns matching semantic documents in ascending seq order. + */ +async filterEvents( sessionId: SessionId, filters: readonly SessionEventResultFilter[], ): Promise + +/** + * Read one session's complete current model surface from one corpus observation. + * @param sessionId - live-preferred session id to read. + * @returns cloned header, current surface, and raw-log capture boundary. + * @throws when source resolution fails or the session surface is invalid. + */ +async readSurface(sessionId: SessionId): Promise + +/** + * Trace known ancestry and descendants from one corpus observation. + * @param sessionId - logical session id to trace. + * @param signal - optional cancellation for persistence listing. + * @returns a complete lineage or an explicit unresolved parent boundary. + * @throws when corpus resolution fails, the target is absent, or its known ancestry cycles. + */ +async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Trace one event's direct positional and provenance relationships. + * @param request - target session id and event seq. + * @param signal - optional cancellation for persisted source resolution. + * @returns source header, direct links, and the target's positional replacement chain. + * @throws when source resolution fails, the target is absent, or surface/provenance validation fails. + */ +async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise + +/** + * Read one full event plus a bounded raw-log context window. + * @param request - target session/seq and context sizes. + * @param signal - optional cancellation for persisted source resolution. + * @returns cloned target and neighboring events. + */ +async readEvent(request: SessionEventReadRequest, signal?: AbortSignal): Promise +``` + +Types: [SessionId](core.md) · [SessionTitleSnapshot](session-title.md) + +Source: [`packages/session-query/session-query/src/index.ts:81`](../../packages/session-query/session-query/src/index.ts) + diff --git a/docs/subsystems/session-query.zh.md b/docs/subsystems/session-query.zh.md index 4c3dd4d435..b770514757 100644 --- a/docs/subsystems/session-query.zh.md +++ b/docs/subsystems/session-query.zh.md @@ -354,3 +354,141 @@ type SessionQueryErrorCode = | 'SESSION_QUERY_STALE_CURSOR' | 'SESSION_QUERY_SOURCE_CONFLICT' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionQuery` — `SessionQueryService` (abstract seam) + +Unified live-preferred session query service. + +Exact reads, filters, and traces are backend-independent concrete behavior. A backend implements full-text observation, reconciliation, ranking, cursor generations, and query execution on the same `ctx.sessionQuery` service. + +```ts cordis-catalog +/** + * Search the live-preferred logical corpus and group by session. + * @param request - query text, metadata filters, page size, and cursor. + * @param exec - optional cancellation control. + * @returns session hits ranked by their strongest matching event. + */ +abstract searchSessions( request: SessionSearchRequest, exec?: SessionSearchExecContext, ): Promise> + +/** + * Search events within one live-preferred logical session. + * @param request - target session, query text, filters, page size, and cursor. + * @param exec - optional cancellation control. + * @returns matching event hits and their target header from one indexed generation. + */ +abstract searchEvents( request: SessionEventSearchRequest, exec?: SessionSearchExecContext, ): Promise + +/** + * List the complete logical corpus using live-preferred records. + * @param signal - optional cancellation for persistence listing. + * @returns deterministic newest-first cloned session records. + */ +listSessions(signal?: AbortSignal): Promise + +/** + * Read and replay-validate one complete logical session log without making it live. + * @param sessionId - live or persisted session id to read. + * @returns cloned header and complete raw event log from one observation. + * @throws when persistence, header compatibility, or replay validation fails. + */ +async readSession(sessionId: SessionId): Promise + +/** + * Filter the complete logical corpus with provider-independent predicates. + * @param filters - ANDed session metadata and availability clauses. + * @param signal - optional cancellation for persistence listing. + * @returns matching cloned records in deterministic newest-first order. + */ +async filterSessions( filters: readonly SessionResultFilter[], signal?: AbortSignal, ): Promise + +/** + * Fold the latest log-backed title from one live-preferred logical session. + * @param sessionId - live or persisted session id to read. + * @param signal - optional cancellation for source resolution and title folding. + * @returns latest title snapshot, or `undefined` when the log has no title event. + */ +async readTitle( sessionId: SessionId, signal?: AbortSignal, ): Promise + +/** + * Fold the latest title and return its source header from one corpus observation. + * @param sessionId - live or persisted session id to read. + * @param signal - optional cancellation for source resolution and title folding. + * @returns cloned source header and optional latest title snapshot. + */ +async readTitleSnapshot( sessionId: SessionId, signal?: AbortSignal, ): Promise + +/** + * Fold titles for unique sessions from one cancellable corpus observation. + * + * Results preserve first-occurrence input order. Operational failures stay + * isolated per session, while cancellation rejects the complete operation. + * @param sessionIds - live or persisted session ids to observe. + * @param signal - optional cancellation shared by all source reads. + * @returns one fulfilled or rejected result per unique requested id. + */ +async readTitleSnapshots( sessionIds: readonly SessionId[], signal?: AbortSignal, ): Promise + +/** + * List lightweight raw-log event records for one logical session. + * @param sessionId - live-preferred session id to read. + * @returns event records in ascending seq order. + */ +async listEvents(sessionId: SessionId): Promise + +/** + * Scan first-party semantic event documents with provider-independent filters. + * @param sessionId - live-preferred session id to scan. + * @param filters - ANDed metadata and literal-text predicates. + * @returns matching semantic documents in ascending seq order. + */ +async filterEvents( sessionId: SessionId, filters: readonly SessionEventResultFilter[], ): Promise + +/** + * Read one session's complete current model surface from one corpus observation. + * @param sessionId - live-preferred session id to read. + * @returns cloned header, current surface, and raw-log capture boundary. + * @throws when source resolution fails or the session surface is invalid. + */ +async readSurface(sessionId: SessionId): Promise + +/** + * Trace known ancestry and descendants from one corpus observation. + * @param sessionId - logical session id to trace. + * @param signal - optional cancellation for persistence listing. + * @returns a complete lineage or an explicit unresolved parent boundary. + * @throws when corpus resolution fails, the target is absent, or its known ancestry cycles. + */ +async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Trace one event's direct positional and provenance relationships. + * @param request - target session id and event seq. + * @param signal - optional cancellation for persisted source resolution. + * @returns source header, direct links, and the target's positional replacement chain. + * @throws when source resolution fails, the target is absent, or surface/provenance validation fails. + */ +async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise + +/** + * Read one full event plus a bounded raw-log context window. + * @param request - target session/seq and context sizes. + * @param signal - optional cancellation for persisted source resolution. + * @returns cloned target and neighboring events. + */ +async readEvent(request: SessionEventReadRequest, signal?: AbortSignal): Promise +``` + +Types: [SessionId](core.md) · [SessionTitleSnapshot](session-title.md) + +Source: [`packages/session-query/session-query/src/index.ts:81`](../../packages/session-query/session-query/src/index.ts) + diff --git a/docs/subsystems/session-reference.i18n.yaml b/docs/subsystems/session-reference.i18n.yaml index f119c67913..980c1a1a2d 100644 --- a/docs/subsystems/session-reference.i18n.yaml +++ b/docs/subsystems/session-reference.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-reference.md -session-reference.md: 5375677f6a1748909743ca76d5191cb9e736a40a -session-reference.zh.md: 3ff4a1719926bda0a9111482a7778a8c94553370 +session-reference.md: 60e29c56ea372e100009b94caedea5327308432a +session-reference.zh.md: 3ae9264abc15848646e5397dfcbeff764c038918 diff --git a/docs/subsystems/session-reference.md b/docs/subsystems/session-reference.md index 5375677f6a..60e29c56ea 100644 --- a/docs/subsystems/session-reference.md +++ b/docs/subsystems/session-reference.md @@ -65,3 +65,44 @@ type SessionReferenceErrorCode = | 'SESSION_REFERENCE_BUDGET_EXCEEDED' | 'SESSION_REFERENCE_CANCELLED' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionReferences` — `SessionReferenceService` + +Exact-read consumer that prepares immutable cross-session message context. + +```ts cordis-catalog +/** + * List reference candidates, ranked by working-directory affinity. + * @param agent - target agent; self is excluded and its cwd drives ranking. + * @param query - optional case-insensitive session-id/cwd/title substring. + * @param limit - optional positive result cap. + * @param signal - optional cancellation boundary for host autocomplete teardown. + * @returns candidates labeled by latest title or, when absent, session id. + */ +async listCandidates( agent: Agent, query: string = '', limit: number = this.config.candidateLimit, signal?: AbortSignal, ): Promise + +/** + * Snapshot all references before enqueue and return one aggregated durable context. + * @param agent - target agent; references to it are rejected. + * @param content - already host-normalized readable message content. + * @param references - structured source sessions in mention order. + * @param signal - optional cancellation boundary for host request teardown. + * @returns detached content and optional referenced-session context. + */ +async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise +``` + +Types: [Agent](core.md) · [ContentBlock](core.md) + +Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) + diff --git a/docs/subsystems/session-reference.zh.md b/docs/subsystems/session-reference.zh.md index 3ff4a17199..3ae9264abc 100644 --- a/docs/subsystems/session-reference.zh.md +++ b/docs/subsystems/session-reference.zh.md @@ -65,3 +65,44 @@ type SessionReferenceErrorCode = | 'SESSION_REFERENCE_BUDGET_EXCEEDED' | 'SESSION_REFERENCE_CANCELLED' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionReferences` — `SessionReferenceService` + +Exact-read consumer that prepares immutable cross-session message context. + +```ts cordis-catalog +/** + * List reference candidates, ranked by working-directory affinity. + * @param agent - target agent; self is excluded and its cwd drives ranking. + * @param query - optional case-insensitive session-id/cwd/title substring. + * @param limit - optional positive result cap. + * @param signal - optional cancellation boundary for host autocomplete teardown. + * @returns candidates labeled by latest title or, when absent, session id. + */ +async listCandidates( agent: Agent, query: string = '', limit: number = this.config.candidateLimit, signal?: AbortSignal, ): Promise + +/** + * Snapshot all references before enqueue and return one aggregated durable context. + * @param agent - target agent; references to it are rejected. + * @param content - already host-normalized readable message content. + * @param references - structured source sessions in mention order. + * @param signal - optional cancellation boundary for host request teardown. + * @returns detached content and optional referenced-session context. + */ +async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise +``` + +Types: [Agent](core.md) · [ContentBlock](core.md) + +Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) + diff --git a/docs/subsystems/session-title.i18n.yaml b/docs/subsystems/session-title.i18n.yaml index c85d4dfac1..8b58415234 100644 --- a/docs/subsystems/session-title.i18n.yaml +++ b/docs/subsystems/session-title.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-title.md -session-title.md: 7bced67f07df3f00471f02b766922cdc3e4fd29e -session-title.zh.md: e8e501d3aafee49395da3d9a8efd4e91a724f09e +session-title.md: 3a2df284f2af14def284e502f4e37daaf4d7a262 +session-title.zh.md: 3a74baa9005459b35b658df95bdd7b57256b3e2e diff --git a/docs/subsystems/session-title.md b/docs/subsystems/session-title.md index 7bced67f07..3a2df284f2 100644 --- a/docs/subsystems/session-title.md +++ b/docs/subsystems/session-title.md @@ -144,3 +144,61 @@ interface SessionTitleProvider { generate(request: SessionTitleProviderRequest): Promise } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionTitle` — `SessionTitleService` + +Log-backed title fold plus asynchronous fallback generation. + +```ts cordis-catalog +/** + * Read the latest folded title from one live or replayed session. + * @param session - session whose log is the title source of truth. + * @returns latest title snapshot, or `undefined` before eligible input. + */ +get(session: Session): SessionTitleSnapshot | undefined + +/** + * Accept an explicit user title. Appends a `session/title` event with the + * `user` source, which pins the title: in-flight automatic generation is + * superseded and later user messages schedule none (an explicit + * {@link SessionTitleService.refresh} remains the deliberate unpin). + * @param session - exact live session to rename. + * @param title - raw user input; normalized before acceptance. + * @returns the accepted title snapshot. + * @throws {SessionTitleInvalidError} when the title normalizes to empty. + * @throws {Error} when the session is not live or the service is disposed. + */ +rename(session: Session, title: string): SessionTitleSnapshot + +/** + * Explicitly retry the registered provider, or materialize the built-in + * fallback when no provider is registered. + * @param session - exact live session to refresh. + * @param signal - optional caller cancellation. + * @returns latest accepted title, or `undefined` when no eligible text exists. + */ +async refresh(session: Session, signal?: AbortSignal): Promise + +/** + * Register the sole optional title provider. Disposal aborts its pending and + * active work before another provider may register. + * @param provider - provider identity, cadence, and generation function. + * @returns exact Cordis effect disposer, which settles after active calls quiesce. + */ +register(provider: SessionTitleProvider): () => Promise +``` + +Types: [Session](session.md) + +Source: [`packages/session/session-title/src/index.ts:261`](../../packages/session/session-title/src/index.ts) + diff --git a/docs/subsystems/session-title.zh.md b/docs/subsystems/session-title.zh.md index e8e501d3aa..3a74baa900 100644 --- a/docs/subsystems/session-title.zh.md +++ b/docs/subsystems/session-title.zh.md @@ -144,3 +144,61 @@ interface SessionTitleProvider { generate(request: SessionTitleProviderRequest): Promise } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessionTitle` — `SessionTitleService` + +Log-backed title fold plus asynchronous fallback generation. + +```ts cordis-catalog +/** + * Read the latest folded title from one live or replayed session. + * @param session - session whose log is the title source of truth. + * @returns latest title snapshot, or `undefined` before eligible input. + */ +get(session: Session): SessionTitleSnapshot | undefined + +/** + * Accept an explicit user title. Appends a `session/title` event with the + * `user` source, which pins the title: in-flight automatic generation is + * superseded and later user messages schedule none (an explicit + * {@link SessionTitleService.refresh} remains the deliberate unpin). + * @param session - exact live session to rename. + * @param title - raw user input; normalized before acceptance. + * @returns the accepted title snapshot. + * @throws {SessionTitleInvalidError} when the title normalizes to empty. + * @throws {Error} when the session is not live or the service is disposed. + */ +rename(session: Session, title: string): SessionTitleSnapshot + +/** + * Explicitly retry the registered provider, or materialize the built-in + * fallback when no provider is registered. + * @param session - exact live session to refresh. + * @param signal - optional caller cancellation. + * @returns latest accepted title, or `undefined` when no eligible text exists. + */ +async refresh(session: Session, signal?: AbortSignal): Promise + +/** + * Register the sole optional title provider. Disposal aborts its pending and + * active work before another provider may register. + * @param provider - provider identity, cadence, and generation function. + * @returns exact Cordis effect disposer, which settles after active calls quiesce. + */ +register(provider: SessionTitleProvider): () => Promise +``` + +Types: [Session](session.md) + +Source: [`packages/session/session-title/src/index.ts:261`](../../packages/session/session-title/src/index.ts) + diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index dd7135e8d3..a7e81056e5 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session.md -session.md: 5d54b03df8ec3345e8bf04702f242e3aacf8ec39 -session.zh.md: 5a867cfe302bf02994bfe5a5a704bed55222e8ea +session.md: 28b3f959b47a24373860c2334f9273286d072c95 +session.zh.md: 5870265f55892231700b65ac6ed69d827a0ab1ec diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 5d54b03df8..28b3f959b4 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -343,7 +343,7 @@ interface SurfaceFoldResult { ## `Session` public API -The body-stripped declaration keeps the plain class's detached factory, state accessors, append boundary, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` service catalog](../cordis-catalog/services.md#ctxsessions--sessionstore). +The body-stripped declaration keeps the plain class's detached factory, state accessors, append boundary, and history projections synchronized with source. Store operations remain in the generated [`ctx.sessions` section](#ctxsessions--sessionstore). ```ts public-api /** @@ -586,3 +586,247 @@ The hook bridges' `hook/invoked` / `hook/result` provenance pairs (from `@deepse What a persistence backend relies on: the durable log persists every event losslessly, **including** `assistant/chunk` — `seq` must stay contiguous, so chunks cannot be filtered out of the canonical log. A backend may choose its own storage encoding for an event batch as long as `load` returns the exact appended events (the JSONL backend's default packed chunk rows are such an encoding — see [persistence.md](persistence.md)). All `event.data` must be JSON-serializable; `Session.append` enforces this at the source (throwing on non-serializable data), so a bad event never enters the log and `session.events` always equals what a backend can persist. Adding an event type that carries non-serializable data, corrupts core execution nesting, or violates its owner's declared relation is a breaking change to the on-disk format. The backends that consume this contract are on [persistence.md](persistence.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessions` — `SessionStore` + +In-memory session store (`ctx.sessions`). + +Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. + +```ts cordis-catalog +/** + * Create a session owned by the calling fiber: disposing that fiber stops + * event notification and removes the session from the store. `options.seed` + * populates the session with a copy of those events (replay/fork); + * `options.meta` attaches creation metadata (validated absolute `cwd`, seed + * and parent lineage, and delegation depth) as the immutable + * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`). + * + * For an agent whose session must be torn down IN ORDER with its loop (so the + * loop's final events are published before the store attachment ends), do NOT use this + * — fold the session lifecycle into the agent's own effect via + * {@link prepare} + {@link enter} + {@link announce} (see + * `dsh-agent-loop`'s creation transaction). + * + * @param id - the session id; omitted, the store mints `session-`. + * @param options - seed events and/or creation metadata for the header. + * @returns the live session, already entered and announced. + * @throws if a session with `id` already exists, metadata is not a plain + * lossless-JSON record with valid scalar fields, or `meta.cwd` is a + * non-absolute path (storage backends key directories off it). + */ +create(id?: SessionId, options?: CreateSessionOptions): Session + +/** + * Build a session WITHOUT entering it into the store — validate the id/cwd and + * construct the {@link Session} (with its immutable {@link SessionHeader}). + * Pairs with {@link enter} + {@link announce}: a caller that owns a composite + * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE + * effect so a fiber unload tears the session + agent down as a single ORDERED + * chain rather than as racing sibling effects — which would remove the publication hooks + * before the driver's closing events commit, dropping them. + * + * @param id - the session id; omitted, the store mints `session-`. + * @param options - seed events and/or creation metadata for the header. With + * `seedSource: 'persistence'`, metadata and events must be fresh detached + * graphs whose ownership transfers to this call: they are validated and + * frozen in place through {@link Session.fromRestore}, so the caller must + * retain no mutable aliases. + * @returns the constructed session, NOT yet in the store. + * @throws if a session with `id` already exists, metadata is not a plain + * lossless-JSON record with valid scalar fields, or `meta.cwd` is a + * non-absolute path. + */ +prepare(id?: SessionId, options?: PrepareSessionOptions): Session + +/** + * Enter a {@link prepare}d session into the store: install the module-private + * append publication hooks and add it to the store. Returns the DETACH + * disposer (hooks + store removal). Does NOT emit `session/created` — + * the caller yields this disposer inside its effect and THEN calls + * {@link announce}, so a throwing `session/created` listener rolls the attach + * back instead of leaking it. + * + * Re-checks the id for a duplicate: `prepare` and `enter` are public + * cross-package primitives and a caller may interleave arbitrary work (or + * another create) between them, so a stale prepared session must NOT overwrite + * a live store entry of the same id — its detach disposer would later delete + * the REAL session. The {@link create} convenience and the agent factory call + * the two back-to-back so they never trip this, but the public seam cannot + * assume that. + * + * @param session - a {@link prepare}d session not yet in the store. + * @returns the detach disposer (publication hooks + store removal). When called from + * a synchronous `session/created` listener, removal and disposal wait until + * that creation dispatch unwinds. + * @throws if a session with this id is already in the store. + */ +enter(session: Session): () => void + +/** Emit `session/created` exactly once for an {@link enter}ed session (with + * the carrier {@link enter} captured). Separate from {@link enter} so the + * caller can yield the detach disposer first (rollback safety — see + * {@link enter}). + * @param session - the entered session to announce to listeners. + * @throws if the session is not live or its announcement already began, + * including a reentrant call from a creation listener. */ +announce(session: Session): void + +/** + * Dispatch the awaited `session/flush` durability checkpoint for `session`, + * with the carrier captured at {@link enter}. THE flush entry point: the + * store owns the carrier, so callers (the checkpoint policy's per-request + * barrier, goal-session's idle checkpoint, teardown drains, and consumers + * that flush themselves before reading storage) must come through here + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, + * one spelling, and the scoped-dispatch invariant can pin it. + * @param session - the session whose buffered events must reach durable storage. + * @returns whether at least one durability listener participated, after every + * listener has settled successfully. + * @throws the first registered listener failure after every listener settles. + */ +async flush(session: Session): Promise + +/** + * Look up a live session. + * @param id - the session id to look up. + * @returns the session, or undefined when no live session has that id. + */ +get(id: SessionId): Session | undefined + +/** + * All live sessions, in creation order. + * @returns a fresh array; mutating it does not affect the store. + */ +list(): Session[] + +/** + * Create a live child session from a stable prefix of a live source. + * `boundary` is an inclusive source event seq; omitted means the source's + * current last event. The selected slice may end with a between-turn event + * but must not end inside an open turn. + * + * @param source - Live source session object or id. + * @param boundary - Inclusive source event seq to fork through; omitted means + * the source's current last event, and omitted on an empty source forks an + * empty child. + * @param childSessionId - Optional child session id; omitted delegates to + * `SessionStore`'s id policy. + * @returns The created live child session. + */ +fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session +``` + +Types: [CreateSessionOptions](persistence.md) · [PrepareSessionOptions](persistence.md) · [SessionId](core.md) + +Source: [`packages/core/session/src/index.ts:807`](../../packages/core/session/src/index.ts) + + + +### `session/*` events + + + +#### `session/created` — emit + +Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context. + +```ts cordis-catalog +/** + * Creation announcement during session publication. A synchronous throw vetoes and rolls + * back with a paired disposal; detach requested during dispatch is deferred. + * A returned-promise rejection is logged but cannot retroactively veto this + * synchronous boundary. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners + * receive only sessions entered through that agent's context. + * @param session - the session just entered and announced. + * @dshScopeScan unsupported + * @mode emit + */ +'session/created'(this: Scoped, session: Session): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:74`](../../packages/core/session/src/index.ts) + + + +#### `session/disposed` — emit + +Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. + +```ts cordis-catalog +/** + * Emitted once when an announced session leaves the store, including + * publication rollback, but never for an entry whose creation announcement + * did not begin. Listener failures are logged and contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. + * @param session - the session that is no longer live in the store. + * @dshScopeScan unsupported + * @mode emit + */ +'session/disposed'(this: Scoped, session: Session): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:84`](../../packages/core/session/src/index.ts) + + + +#### `session/event` — emit + +Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context. + +```ts cordis-catalog +/** + * Post-commit, fire-and-forget append feed. The listener snapshot resolves + * before the log push, but callbacks run after it; observer failures are + * logged and contained without making the committed append fail. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners + * receive only events from sessions entered through that agent's context. + * @param session - the session whose log grew. + * @param event - the appended event, exactly as recorded. + * @dshScopeScan unsupported + * @mode emit + */ +'session/event'(this: Scoped, session: Session, event: SessionEvent): void +``` + +Types: [Scoped](scope.md) · [SessionEvent](core.md) + +Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) + + + +#### `session/flush` — parallel + +Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. + +```ts cordis-catalog +/** + * Awaited parallel durability checkpoint: every listener runs and the + * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch + * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. + * @param session - the session whose buffered events must reach durable storage. + * @dshScopeScan unsupported + * @mode parallel + */ +'session/flush'(this: Scoped, session: Session): Promise | void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:105`](../../packages/core/session/src/index.ts) + diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index 5a867cfe30..5870265f55 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -345,7 +345,7 @@ interface SurfaceFoldResult { ## `Session` 公共 API -去除方法体的声明与源码中的普通类保持同步,覆盖其脱离态工厂、状态访问器、追加边界和历史投影。存储操作仍由生成的 [`ctx.sessions` 服务目录](../cordis-catalog/services.md#ctxsessions--sessionstore)记录。 +去除方法体的声明与源码中的普通类保持同步,覆盖其脱离态工厂、状态访问器、追加边界和历史投影。存储操作仍由生成的 [`ctx.sessions` 小节](#ctxsessions--sessionstore)记录。 ```ts public-api /** @@ -590,3 +590,247 @@ interface TurnEndReasonMap { 持久化后端依赖的契约如下:持久日志无损保存每个事件,**包括** `assistant/chunk`;`seq` 必须连续,因此不能从规范日志中过滤分片。后端可以为事件批次选择自己的存储编码,只要 `load` 返回与追加时完全一致的事件即可(JSONL 后端默认启用的打包分片行就是此类编码;见 [persistence.md](persistence.md))。所有 `event.data` 都必须可序列化为 JSON;`Session.append` 会从源头强制这一要求(遇到不可序列化数据时抛出),因此错误事件绝不会进入日志,`session.events` 始终与后端可持久化的内容一致。新增会携带不可序列化数据、破坏核心执行嵌套或违反事件所有方声明关系的事件类型,都会构成磁盘格式的破坏性变更。 消费此契约的后端见 [persistence.md](persistence.md)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.sessions` — `SessionStore` + +In-memory session store (`ctx.sessions`). + +Persistence is intentionally not implemented here — persistence plugins subscribe to `session/event` and flush on `session/flush` / dispose. + +```ts cordis-catalog +/** + * Create a session owned by the calling fiber: disposing that fiber stops + * event notification and removes the session from the store. `options.seed` + * populates the session with a copy of those events (replay/fork); + * `options.meta` attaches creation metadata (validated absolute `cwd`, seed + * and parent lineage, and delegation depth) as the immutable + * {@link SessionHeader} (the store fills `version`/`id`/`createdAt`). + * + * For an agent whose session must be torn down IN ORDER with its loop (so the + * loop's final events are published before the store attachment ends), do NOT use this + * — fold the session lifecycle into the agent's own effect via + * {@link prepare} + {@link enter} + {@link announce} (see + * `dsh-agent-loop`'s creation transaction). + * + * @param id - the session id; omitted, the store mints `session-`. + * @param options - seed events and/or creation metadata for the header. + * @returns the live session, already entered and announced. + * @throws if a session with `id` already exists, metadata is not a plain + * lossless-JSON record with valid scalar fields, or `meta.cwd` is a + * non-absolute path (storage backends key directories off it). + */ +create(id?: SessionId, options?: CreateSessionOptions): Session + +/** + * Build a session WITHOUT entering it into the store — validate the id/cwd and + * construct the {@link Session} (with its immutable {@link SessionHeader}). + * Pairs with {@link enter} + {@link announce}: a caller that owns a composite + * `ctx.effect` (the agent factory) folds the session lifecycle into that ONE + * effect so a fiber unload tears the session + agent down as a single ORDERED + * chain rather than as racing sibling effects — which would remove the publication hooks + * before the driver's closing events commit, dropping them. + * + * @param id - the session id; omitted, the store mints `session-`. + * @param options - seed events and/or creation metadata for the header. With + * `seedSource: 'persistence'`, metadata and events must be fresh detached + * graphs whose ownership transfers to this call: they are validated and + * frozen in place through {@link Session.fromRestore}, so the caller must + * retain no mutable aliases. + * @returns the constructed session, NOT yet in the store. + * @throws if a session with `id` already exists, metadata is not a plain + * lossless-JSON record with valid scalar fields, or `meta.cwd` is a + * non-absolute path. + */ +prepare(id?: SessionId, options?: PrepareSessionOptions): Session + +/** + * Enter a {@link prepare}d session into the store: install the module-private + * append publication hooks and add it to the store. Returns the DETACH + * disposer (hooks + store removal). Does NOT emit `session/created` — + * the caller yields this disposer inside its effect and THEN calls + * {@link announce}, so a throwing `session/created` listener rolls the attach + * back instead of leaking it. + * + * Re-checks the id for a duplicate: `prepare` and `enter` are public + * cross-package primitives and a caller may interleave arbitrary work (or + * another create) between them, so a stale prepared session must NOT overwrite + * a live store entry of the same id — its detach disposer would later delete + * the REAL session. The {@link create} convenience and the agent factory call + * the two back-to-back so they never trip this, but the public seam cannot + * assume that. + * + * @param session - a {@link prepare}d session not yet in the store. + * @returns the detach disposer (publication hooks + store removal). When called from + * a synchronous `session/created` listener, removal and disposal wait until + * that creation dispatch unwinds. + * @throws if a session with this id is already in the store. + */ +enter(session: Session): () => void + +/** Emit `session/created` exactly once for an {@link enter}ed session (with + * the carrier {@link enter} captured). Separate from {@link enter} so the + * caller can yield the detach disposer first (rollback safety — see + * {@link enter}). + * @param session - the entered session to announce to listeners. + * @throws if the session is not live or its announcement already began, + * including a reentrant call from a creation listener. */ +announce(session: Session): void + +/** + * Dispatch the awaited `session/flush` durability checkpoint for `session`, + * with the carrier captured at {@link enter}. THE flush entry point: the + * store owns the carrier, so callers (the checkpoint policy's per-request + * barrier, goal-session's idle checkpoint, teardown drains, and consumers + * that flush themselves before reading storage) must come through here + * rather than dispatch a raw `ctx.parallel('session/flush', …)` — one owner, + * one spelling, and the scoped-dispatch invariant can pin it. + * @param session - the session whose buffered events must reach durable storage. + * @returns whether at least one durability listener participated, after every + * listener has settled successfully. + * @throws the first registered listener failure after every listener settles. + */ +async flush(session: Session): Promise + +/** + * Look up a live session. + * @param id - the session id to look up. + * @returns the session, or undefined when no live session has that id. + */ +get(id: SessionId): Session | undefined + +/** + * All live sessions, in creation order. + * @returns a fresh array; mutating it does not affect the store. + */ +list(): Session[] + +/** + * Create a live child session from a stable prefix of a live source. + * `boundary` is an inclusive source event seq; omitted means the source's + * current last event. The selected slice may end with a between-turn event + * but must not end inside an open turn. + * + * @param source - Live source session object or id. + * @param boundary - Inclusive source event seq to fork through; omitted means + * the source's current last event, and omitted on an empty source forks an + * empty child. + * @param childSessionId - Optional child session id; omitted delegates to + * `SessionStore`'s id policy. + * @returns The created live child session. + */ +fork(source: SessionForkSource, boundary?: number, childSessionId?: SessionId): Session +``` + +Types: [CreateSessionOptions](persistence.md) · [PrepareSessionOptions](persistence.md) · [SessionId](core.md) + +Source: [`packages/core/session/src/index.ts:807`](../../packages/core/session/src/index.ts) + + + +### `session/*` events + + + +#### `session/created` — emit + +Creation announcement during session publication. A synchronous throw vetoes and rolls back with a paired disposal; detach requested during dispatch is deferred. A returned-promise rejection is logged but cannot retroactively veto this synchronous boundary. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only sessions entered through that agent's context. + +```ts cordis-catalog +/** + * Creation announcement during session publication. A synchronous throw vetoes and rolls + * back with a paired disposal; detach requested during dispatch is deferred. + * A returned-promise rejection is logged but cannot retroactively veto this + * synchronous boundary. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners + * receive only sessions entered through that agent's context. + * @param session - the session just entered and announced. + * @dshScopeScan unsupported + * @mode emit + */ +'session/created'(this: Scoped, session: Session): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:74`](../../packages/core/session/src/index.ts) + + + +#### `session/disposed` — emit + +Emitted once when an announced session leaves the store, including publication rollback, but never for an entry whose creation announcement did not begin. Listener failures are logged and contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. + +```ts cordis-catalog +/** + * Emitted once when an announced session leaves the store, including + * publication rollback, but never for an entry whose creation announcement + * did not begin. Listener failures are logged and contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the owner scope. + * @param session - the session that is no longer live in the store. + * @dshScopeScan unsupported + * @mode emit + */ +'session/disposed'(this: Scoped, session: Session): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:84`](../../packages/core/session/src/index.ts) + + + +#### `session/event` — emit + +Post-commit, fire-and-forget append feed. The listener snapshot resolves before the log push, but callbacks run after it; observer failures are logged and contained without making the committed append fail. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only events from sessions entered through that agent's context. + +```ts cordis-catalog +/** + * Post-commit, fire-and-forget append feed. The listener snapshot resolves + * before the log push, but callbacks run after it; observer failures are + * logged and contained without making the committed append fail. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners + * receive only events from sessions entered through that agent's context. + * @param session - the session whose log grew. + * @param event - the appended event, exactly as recorded. + * @dshScopeScan unsupported + * @mode emit + */ +'session/event'(this: Scoped, session: Session, event: SessionEvent): void +``` + +Types: [Scoped](scope.md) · [SessionEvent](core.md) + +Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) + + + +#### `session/flush` — parallel + +Awaited parallel durability checkpoint: every listener runs and the caller awaits all of them, with no waterfall veto. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. + +```ts cordis-catalog +/** + * Awaited parallel durability checkpoint: every listener runs and the + * caller awaits all of them, with no waterfall veto. Scope-filtered dispatch + * (`@deepseek-ai/dsh-scope`) reuses the session's owner scope. + * @param session - the session whose buffered events must reach durable storage. + * @dshScopeScan unsupported + * @mode parallel + */ +'session/flush'(this: Scoped, session: Session): Promise | void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/session/src/index.ts:105`](../../packages/core/session/src/index.ts) + diff --git a/docs/subsystems/settings.i18n.yaml b/docs/subsystems/settings.i18n.yaml index f4518f4414..5c40419366 100644 --- a/docs/subsystems/settings.i18n.yaml +++ b/docs/subsystems/settings.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/settings.md -settings.md: bd01c1d28407af9cab26f624a054a010e25a3ddd -settings.zh.md: 1cb7f8b507f29f2b6876fd48b4c37284df235e53 +settings.md: 9256bf9436d2e77093fc8c6a3728b62fc4e8d67f +settings.zh.md: f6d662ebaf35eeb9d046353ea1087b0eb86e7068 diff --git a/docs/subsystems/settings.md b/docs/subsystems/settings.md index bd01c1d284..9256bf9436 100644 --- a/docs/subsystems/settings.md +++ b/docs/subsystems/settings.md @@ -160,3 +160,151 @@ Every committed change — an in-process write or an externally observed provide /** Origin of one committed settings change. */ type SettingsUpdateSource = 'update' | 'provider' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.settings` — `Settings` (abstract seam) + +Abstract settings service. Providers implement raw-document storage (`load`/`persist`) and push external changes through Settings.publish; the base class owns namespace registration, resolution, validation, change detection, and the `settings/updated` commit event. + +```ts cordis-catalog +/** + * Prepare the provider's user-editable document for a native editor. File + * providers may materialize an absent document before returning its path; + * non-file providers return undefined. + * @returns the absolute local document path, or undefined for non-file storage. + */ +prepareDocument(): Promise + +/** + * Register a namespace schema and receive its owner scope. The registration + * is an effect on the calling plugin's fiber: disposing that fiber removes + * the namespace and its observers. An invalid stored section fails the + * registration itself — the earliest point where the schema can judge it. + * @param ns - unique namespace; duplicate registration fails loud. + * @param schema - schemastery schema resolving this namespace's value. + * @param options - composition `base` layer and effect timing. + * @returns the owner scope for reads, observation, and updates. + */ +register(ns: SettingsNamespace, schema: z, options?: SettingsRegisterOptions): SettingsScope + +/** + * Describe every registered namespace for configuration surfaces, including + * the composition `base` and raw user layers so a form can mark which fields + * the user overrode (presence in `user`) and what a reset returns to. + * @param options - redaction switch; wire surfaces must redact. + * @returns one descriptor per registered namespace, in registration order. + */ +describe(options?: SettingsDescribeOptions): SettingsDescriptor[] + +/** + * Read one registered namespace's resolved value. + * @param ns - the namespace to read. + * @returns the resolved value, or `undefined` while unregistered. + */ +get(ns: SettingsNamespace): unknown + +/** + * Merge a patch into one registered namespace's user layer, validate the + * resolved candidate, persist through the provider, then commit and emit. + * A validation failure rejects before anything is persisted. Writes to one + * namespace are serialized: concurrent updates apply in call order, each + * merging over the previous write's committed section. + * @param ns - the registered namespace to update. + * @param patch - plain-object patch over the user section. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise + +/** + * Replace one registered namespace's user section wholesale, validate, + * persist, then commit and emit. Keys absent from `section` fall back to the + * composition `base` and schema defaults — this is the removal/reset path a + * merge-only patch cannot express (`replace({})` re-inherits everything). + * @param ns - the registered namespace to replace. + * @param section - the complete next user section. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise + +/** + * Apply path-addressed edits to one registered namespace's user section, + * validate, persist, then commit and emit. The ops are applied to the + * section as it stands when the write reaches the front of the queue, so a + * caller never has to restate fields it did not touch — and, crucially, + * cannot delete fields it never saw. This is the write path for any caller + * holding a redacted view; `replace` remains the wholesale reset. + * @param ns - the registered namespace to edit. + * @param ops - ordered path edits; later ops observe earlier ones. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise +``` + +Source: [`packages/settings/settings/src/index.ts:387`](../../packages/settings/settings/src/index.ts) + + + +### `settings/*` events + + + +#### `settings/document-updated` — emit + +One registered namespace's RAW user section changed, whether or not the resolved value did. `settings/updated` is the consumer-facing event and stays deep-equal-gated; this one exists for configuration surfaces, which must learn that a field went from inherited to overridden (same resolved value, different meaning) and that their held revision is stale. Listener containment matches `settings/updated`. + +```ts cordis-catalog +/** + * One registered namespace's RAW user section changed, whether or not the + * resolved value did. `settings/updated` is the consumer-facing event and + * stays deep-equal-gated; this one exists for configuration surfaces, + * which must learn that a field went from inherited to overridden (same + * resolved value, different meaning) and that their held revision is + * stale. Listener containment matches `settings/updated`. + * @param ns - the namespace whose stored section changed. + * @param revision - the namespace's new revision. + * @mode emit + */ +'settings/document-updated'(ns: SettingsNamespace, revision: number): void +``` + +Source: [`packages/settings/settings/src/index.ts:170`](../../packages/settings/settings/src/index.ts) + + + +#### `settings/updated` — emit + +Committed change to one registered namespace's resolved value. Emitted after the provider persisted (for `update`) or published (`provider`) the change; never emitted when the resolved value is deep-equal. Listener failures are contained and logged — a sync throw and an async rejection alike — except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. + +```ts cordis-catalog +/** + * Committed change to one registered namespace's resolved value. Emitted + * after the provider persisted (for `update`) or published (`provider`) + * the change; never emitted when the resolved value is deep-equal. + * Listener failures are contained and logged — a sync throw and an async + * rejection alike — except `INVARIANT`-coded failures, which rethrow + * after every listener ran; that rethrow reaches the emitter only from + * synchronous listeners, so invariant checks on this event must not be + * async functions. + * @param ns - the namespace whose resolved value changed. + * @param next - the new resolved value. + * @param prev - the previous resolved value. + * @param source - whether the change entered through `update()` or the provider. + * @mode emit + */ +'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void +``` + +Source: [`packages/settings/settings/src/index.ts:157`](../../packages/settings/settings/src/index.ts) + diff --git a/docs/subsystems/settings.zh.md b/docs/subsystems/settings.zh.md index 1cb7f8b507..f6d662ebaf 100644 --- a/docs/subsystems/settings.zh.md +++ b/docs/subsystems/settings.zh.md @@ -160,3 +160,151 @@ interface SettingsDescribeOptions { /** Origin of one committed settings change. */ type SettingsUpdateSource = 'update' | 'provider' ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.settings` — `Settings` (abstract seam) + +Abstract settings service. Providers implement raw-document storage (`load`/`persist`) and push external changes through Settings.publish; the base class owns namespace registration, resolution, validation, change detection, and the `settings/updated` commit event. + +```ts cordis-catalog +/** + * Prepare the provider's user-editable document for a native editor. File + * providers may materialize an absent document before returning its path; + * non-file providers return undefined. + * @returns the absolute local document path, or undefined for non-file storage. + */ +prepareDocument(): Promise + +/** + * Register a namespace schema and receive its owner scope. The registration + * is an effect on the calling plugin's fiber: disposing that fiber removes + * the namespace and its observers. An invalid stored section fails the + * registration itself — the earliest point where the schema can judge it. + * @param ns - unique namespace; duplicate registration fails loud. + * @param schema - schemastery schema resolving this namespace's value. + * @param options - composition `base` layer and effect timing. + * @returns the owner scope for reads, observation, and updates. + */ +register(ns: SettingsNamespace, schema: z, options?: SettingsRegisterOptions): SettingsScope + +/** + * Describe every registered namespace for configuration surfaces, including + * the composition `base` and raw user layers so a form can mark which fields + * the user overrode (presence in `user`) and what a reset returns to. + * @param options - redaction switch; wire surfaces must redact. + * @returns one descriptor per registered namespace, in registration order. + */ +describe(options?: SettingsDescribeOptions): SettingsDescriptor[] + +/** + * Read one registered namespace's resolved value. + * @param ns - the namespace to read. + * @returns the resolved value, or `undefined` while unregistered. + */ +get(ns: SettingsNamespace): unknown + +/** + * Merge a patch into one registered namespace's user layer, validate the + * resolved candidate, persist through the provider, then commit and emit. + * A validation failure rejects before anything is persisted. Writes to one + * namespace are serialized: concurrent updates apply in call order, each + * merging over the previous write's committed section. + * @param ns - the registered namespace to update. + * @param patch - plain-object patch over the user section. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise + +/** + * Replace one registered namespace's user section wholesale, validate, + * persist, then commit and emit. Keys absent from `section` fall back to the + * composition `base` and schema defaults — this is the removal/reset path a + * merge-only patch cannot express (`replace({})` re-inherits everything). + * @param ns - the registered namespace to replace. + * @param section - the complete next user section. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise + +/** + * Apply path-addressed edits to one registered namespace's user section, + * validate, persist, then commit and emit. The ops are applied to the + * section as it stands when the write reaches the front of the queue, so a + * caller never has to restate fields it did not touch — and, crucially, + * cannot delete fields it never saw. This is the write path for any caller + * holding a redacted view; `replace` remains the wholesale reset. + * @param ns - the registered namespace to edit. + * @param ops - ordered path edits; later ops observe earlier ones. + * @param expectedRevision - the descriptor `revision` the caller read; a + * namespace that moved past it rejects with {@link SettingsConflictError}. + */ +async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise +``` + +Source: [`packages/settings/settings/src/index.ts:387`](../../packages/settings/settings/src/index.ts) + + + +### `settings/*` events + + + +#### `settings/document-updated` — emit + +One registered namespace's RAW user section changed, whether or not the resolved value did. `settings/updated` is the consumer-facing event and stays deep-equal-gated; this one exists for configuration surfaces, which must learn that a field went from inherited to overridden (same resolved value, different meaning) and that their held revision is stale. Listener containment matches `settings/updated`. + +```ts cordis-catalog +/** + * One registered namespace's RAW user section changed, whether or not the + * resolved value did. `settings/updated` is the consumer-facing event and + * stays deep-equal-gated; this one exists for configuration surfaces, + * which must learn that a field went from inherited to overridden (same + * resolved value, different meaning) and that their held revision is + * stale. Listener containment matches `settings/updated`. + * @param ns - the namespace whose stored section changed. + * @param revision - the namespace's new revision. + * @mode emit + */ +'settings/document-updated'(ns: SettingsNamespace, revision: number): void +``` + +Source: [`packages/settings/settings/src/index.ts:170`](../../packages/settings/settings/src/index.ts) + + + +#### `settings/updated` — emit + +Committed change to one registered namespace's resolved value. Emitted after the provider persisted (for `update`) or published (`provider`) the change; never emitted when the resolved value is deep-equal. Listener failures are contained and logged — a sync throw and an async rejection alike — except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions. + +```ts cordis-catalog +/** + * Committed change to one registered namespace's resolved value. Emitted + * after the provider persisted (for `update`) or published (`provider`) + * the change; never emitted when the resolved value is deep-equal. + * Listener failures are contained and logged — a sync throw and an async + * rejection alike — except `INVARIANT`-coded failures, which rethrow + * after every listener ran; that rethrow reaches the emitter only from + * synchronous listeners, so invariant checks on this event must not be + * async functions. + * @param ns - the namespace whose resolved value changed. + * @param next - the new resolved value. + * @param prev - the previous resolved value. + * @param source - whether the change entered through `update()` or the provider. + * @mode emit + */ +'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void +``` + +Source: [`packages/settings/settings/src/index.ts:157`](../../packages/settings/settings/src/index.ts) + diff --git a/docs/subsystems/skills.i18n.yaml b/docs/subsystems/skills.i18n.yaml index 6ab9693cca..84628f50b7 100644 --- a/docs/subsystems/skills.i18n.yaml +++ b/docs/subsystems/skills.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/skills.md -skills.md: d862cbc07135680c2377b4c8c82c80d055344221 -skills.zh.md: 3f8c034ec2aa24b4acdbbc1e2ac27717c21649d5 +skills.md: 78d510fafca0017b9528a215270172a74e9b0de1 +skills.zh.md: e88c9f1ba190eb1e90966d35c7dcc2eb86ecd6cc diff --git a/docs/subsystems/skills.md b/docs/subsystems/skills.md index d862cbc071..78d510fafc 100644 --- a/docs/subsystems/skills.md +++ b/docs/subsystems/skills.md @@ -218,3 +218,93 @@ interface Config { Before each later model step, the consumer applies exact tool visibility and digests the exact rendered entries between the `` tags from a complete snapshot. It derives the comparison baseline from the same entries in the newest recognizable visible catalog message sourced by the plugin. A changed digest appends a durable full replacement through `agent.inject()`; deleting every skill appends an explicit empty replacement. Incomplete snapshots preserve the last-good model view. If compaction hides every historical catalog message, the next complete snapshot re-establishes the current catalog; an empty view with no prior catalog emits nothing. These catalog messages are session history, not World State. The model-facing `skill({ name })` tool validates the kebab-case name, finds the summary in the invocation-neutral catalog, rejects it before loading unless `isModelInvocable` permits access, then rereads the complete definition for the calling agent cwd and rechecks the policy before returning content. It reports an unresolved skill as unknown or no longer available and returns a tool result containing ``, ``, and ``. `resourceBase` resolves explicitly referenced scripts, references, and assets only as needed; the loaded result does not enumerate a skill directory. Body-only edits therefore change later tool calls without producing catalog messages or rewriting earlier tool results. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.skills` — `SkillService` + +Registry of skill providers. It merges provider catalogs with stable first-wins duplicate handling, exposes sorted invocation-neutral summaries, and loads full skill bodies on demand. + +```ts cordis-catalog +/** + * Register a borrowed same-process provider synchronously during plugin apply. Duplicate and + * reserved names throw; remote initialization belongs in `list()`. Fiber disposal unregisters + * the provider and invalidates catalog caches. + * @param create - synchronous factory receiving this registration's lifecycle and invalidation control. + * @returns the exact Cordis effect disposer that unregisters this provider; + * composite effects may yield it directly to preserve teardown ordering. + */ +registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void + +/** + * Register a borrowed readonly runtime skill. Project entries outrank runtime entries, which + * outrank user entries. Same-name runtime entries are first-wins; a duplicate logs a warning and + * receives a no-op disposer so it cannot remove the winner. + * @param skill - the skill definition input; omitted invocation and provider fields receive defaults. + * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches. + */ +register(skill: SkillRegistration): () => void + +/** + * List invocation-neutral skill summaries for a workspace. Consumers apply + * model or user invocation policy at their operational boundary. Lookup + * options and provider candidates are readonly same-process values borrowed + * throughout discovery. + * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. + * @returns all sorted winning summaries. + */ +async list(options: SkillLookupOptions = {}): Promise + +/** + * Observe the current invocation-neutral catalog and whether discovery completed within a stable revision. + * Incomplete observations are never cached, allowing consumers to retain last-good state and + * retry on their next request boundary. + * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. + * @returns sorted summaries plus discovery-completeness state. + */ +async snapshot(options: SkillLookupOptions = {}): Promise + +/** + * Load and validate the winning candidate, passing its opaque discovery locator back to the + * provider. Cancellation is rechecked after selection, including cache hits, and raced against + * loading so an uncooperative provider cannot hang the caller. + * @param name - kebab-case skill name. + * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work. + * @returns the full skill, including body content, or `undefined`. + */ +async get(name: string, options: SkillLookupOptions = {}): Promise +``` + +Source: [`packages/skill/skill/src/index.ts:304`](../../packages/skill/skill/src/index.ts) + + + +### `skills/*` events + + + +#### `skills/change` — emit + +A skill provider, runtime contribution, or provider-backed catalog may have changed. This is an unfiltered invalidation notification; consumers refetch the catalog for their own lookup options. Listener failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * A skill provider, runtime contribution, or provider-backed catalog may + * have changed. This is an unfiltered invalidation notification; consumers + * refetch the catalog for their own lookup options. Listener failures are + * contained and cannot veto the registry mutation. + * @mode emit + */ +'skills/change'(): void +``` + +Source: [`packages/skill/skill/src/index.ts:283`](../../packages/skill/skill/src/index.ts) + diff --git a/docs/subsystems/skills.zh.md b/docs/subsystems/skills.zh.md index 3f8c034ec2..e88c9f1ba1 100644 --- a/docs/subsystems/skills.zh.md +++ b/docs/subsystems/skills.zh.md @@ -218,3 +218,93 @@ interface Config { 在后续每个模型步骤之前,消费方都会应用精确的工具可见性,并对完整快照中 `` 标签之间精确渲染的条目计算 digest。它以该插件所发布、最新一条可识别且仍可见的目录消息中的相同条目作为比较基线。digest 发生变化时,会通过 `agent.inject()` 追加一条持久的完整目录替换;删除所有 skill 时会追加一条显式的空替换。不完整快照会保留上一份可用模型视图。如果压缩(compaction)隐藏了所有历史目录消息,下一份完整快照会重新建立当前目录;如果视图为空且从未发布目录,则不发送任何内容。这些目录消息属于会话历史,而非 World State。 面向模型的 `skill({ name })` 工具校验 kebab-case 名称,在与调用策略无关的目录中查找摘要,并在加载前通过 `isModelInvocable` 拒绝无权访问的 skill;随后它根据调用方 agent 的 cwd 重新读取完整定义,并在返回内容前再次检查策略。该工具将无法解析的 skill 报告为未知或已不可用,并返回包含 ``、`` 和 `` 的工具结果。`resourceBase` 仅按需解析显式引用的脚本、参考资料和资产;加载结果不枚举 skill 目录。因此,仅修改正文会改变后续工具调用,而不会生成目录消息或改写先前工具结果。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.skills` — `SkillService` + +Registry of skill providers. It merges provider catalogs with stable first-wins duplicate handling, exposes sorted invocation-neutral summaries, and loads full skill bodies on demand. + +```ts cordis-catalog +/** + * Register a borrowed same-process provider synchronously during plugin apply. Duplicate and + * reserved names throw; remote initialization belongs in `list()`. Fiber disposal unregisters + * the provider and invalidates catalog caches. + * @param create - synchronous factory receiving this registration's lifecycle and invalidation control. + * @returns the exact Cordis effect disposer that unregisters this provider; + * composite effects may yield it directly to preserve teardown ordering. + */ +registerProvider(create: (control: SkillProviderControl) => SkillProvider): () => void + +/** + * Register a borrowed readonly runtime skill. Project entries outrank runtime entries, which + * outrank user entries. Same-name runtime entries are first-wins; a duplicate logs a warning and + * receives a no-op disposer so it cannot remove the winner. + * @param skill - the skill definition input; omitted invocation and provider fields receive defaults. + * @returns the exact Cordis effect disposer, preserving composite teardown order and invalidating caches. + */ +register(skill: SkillRegistration): () => void + +/** + * List invocation-neutral skill summaries for a workspace. Consumers apply + * model or user invocation policy at their operational boundary. Lookup + * options and provider candidates are readonly same-process values borrowed + * throughout discovery. + * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. + * @returns all sorted winning summaries. + */ +async list(options: SkillLookupOptions = {}): Promise + +/** + * Observe the current invocation-neutral catalog and whether discovery completed within a stable revision. + * Incomplete observations are never cached, allowing consumers to retain last-good state and + * retry on their next request boundary. + * @param options - lookup options; `cwd` selects project roots and `signal` cancels discovery. + * @returns sorted summaries plus discovery-completeness state. + */ +async snapshot(options: SkillLookupOptions = {}): Promise + +/** + * Load and validate the winning candidate, passing its opaque discovery locator back to the + * provider. Cancellation is rechecked after selection, including cache hits, and raced against + * loading so an uncooperative provider cannot hang the caller. + * @param name - kebab-case skill name. + * @param options - lookup options; `cwd` selects workspace-sensitive skills and `signal` cancels work. + * @returns the full skill, including body content, or `undefined`. + */ +async get(name: string, options: SkillLookupOptions = {}): Promise +``` + +Source: [`packages/skill/skill/src/index.ts:304`](../../packages/skill/skill/src/index.ts) + + + +### `skills/*` events + + + +#### `skills/change` — emit + +A skill provider, runtime contribution, or provider-backed catalog may have changed. This is an unfiltered invalidation notification; consumers refetch the catalog for their own lookup options. Listener failures are contained and cannot veto the registry mutation. + +```ts cordis-catalog +/** + * A skill provider, runtime contribution, or provider-backed catalog may + * have changed. This is an unfiltered invalidation notification; consumers + * refetch the catalog for their own lookup options. Listener failures are + * contained and cannot veto the registry mutation. + * @mode emit + */ +'skills/change'(): void +``` + +Source: [`packages/skill/skill/src/index.ts:283`](../../packages/skill/skill/src/index.ts) + diff --git a/docs/subsystems/spill.i18n.yaml b/docs/subsystems/spill.i18n.yaml index 17f210bc85..67b938a93a 100644 --- a/docs/subsystems/spill.i18n.yaml +++ b/docs/subsystems/spill.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/spill.md -spill.md: a798d8143b2849dc0cf49d04e7019ce796cdee45 -spill.zh.md: 1167c6f985dbc204dc7166b0fb5854dcf55bc72f +# pnpm run verify-translation-pairing --write docs/subsystems/spill.md +spill.md: 41a3fdaf7d9b15fbc6d479724f77f579b8a98738 +spill.zh.md: 02a6fc4e88dfcfdb47f7639d988fc09c6bc42c69 diff --git a/docs/subsystems/spill.md b/docs/subsystems/spill.md index a798d8143b..41a3fdaf7d 100644 --- a/docs/subsystems/spill.md +++ b/docs/subsystems/spill.md @@ -83,3 +83,35 @@ type SpillLocator = Branded<'SpillLocator'> `SpillStore` (`ctx.spillStore`, defined in [`packages/spill/spill/src/index.ts`](../../packages/spill/spill/src/index.ts)) is a one-method abstract service: `saveText(input) → Promise`. It persists the FULL `content` and REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable). The seam owns storage only: no retention policy, no tool-result replacement, no retrieval/search API. The local backend ([dsh-spill-local](../../packages/spill/spill-local)) writes under `/session-/-` — a configured or lazily-created private (0700) root, a `sha256(sessionId)` session subdir, and an exclusive owner-only (`open(path, 'wx', 0o600)`) write so a planted symlink cannot redirect it. Its `locator` is the local path and its `retrievalHint` tells the model to use `read` or `grep` on that path. The policy consumer ([dsh-spill-policy](../../packages/spill/spill-policy)) replaces an over-`maxInlineBytes` plain-text final result with a retention-library head/tail preview plus the spill reference, best-effort: a save failure keeps the original inline result rather than turning a successful call into an `isError`. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.spillStore` — `SpillStore` (abstract seam) + +Abstract spill storage service. Subclass, implement saveText, and load the subclass as a plugin — it registers as `ctx.spillStore` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). + +Semantics every implementation must honor: + +- saveText persists the FULL `content` verbatim and returns an opaque locator, exact byte length, and model-facing retrieval guidance. +- Storage is scoped by the request's SaveTextSpill.owner session; the backend chooses a private (not world-readable) location and a collision-free name derived from — never equal to — the caller's `suggestedName`. +- `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable); the caller decides how to degrade (the spill policy treats a rejection as best-effort and keeps the inline result). + +```ts cordis-catalog +/** + * Persist `input.content` to a session-scoped spill artifact. + * @param input - the owner, provenance, suggested name, and full text to save. + * @returns the saved artifact's {@link SpillRef}; rejects on a storage failure. + */ +abstract saveText(input: SaveTextSpill): Promise +``` + +Source: [`packages/spill/spill/src/index.ts:45`](../../packages/spill/spill/src/index.ts) + diff --git a/docs/subsystems/spill.zh.md b/docs/subsystems/spill.zh.md index 1167c6f985..02a6fc4e88 100644 --- a/docs/subsystems/spill.zh.md +++ b/docs/subsystems/spill.zh.md @@ -83,3 +83,35 @@ type SpillLocator = Branded<'SpillLocator'> `SpillStore`(`ctx.spillStore`,定义于 [`packages/spill/spill/src/index.ts`](../../packages/spill/spill/src/index.ts))是只有一个方法的抽象服务:`saveText(input) → Promise`。它持久保存完整的 `content`,并在实际存储失败(权限、ENOSPC、后端不可用)时拒绝。该 seam 只负责存储:不负责保留策略、工具结果替换或检索/搜索 API。 本地后端([dsh-spill-local](../../packages/spill/spill-local))写入 `/session-/-`:根目录是已配置或延迟创建的私有(0700)目录,会话子目录采用 `sha256(sessionId)`,并通过排他的仅所有者可访问写入(`open(path, 'wx', 0o600)`)防止预先植入的符号链接重定向写入。其 `locator` 是本地路径,`retrievalHint` 则告知模型在该路径上使用 `read` 或 `grep`。策略消费方([dsh-spill-policy](../../packages/spill/spill-policy))会把超过 `maxInlineBytes` 的纯文本最终结果替换为保留库生成的首尾预览和落盘引用;该过程尽力而为:保存失败时保留原始内联结果,而不会把成功的调用变成 `isError`。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.spillStore` — `SpillStore` (abstract seam) + +Abstract spill storage service. Subclass, implement saveText, and load the subclass as a plugin — it registers as `ctx.spillStore` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). + +Semantics every implementation must honor: + +- saveText persists the FULL `content` verbatim and returns an opaque locator, exact byte length, and model-facing retrieval guidance. +- Storage is scoped by the request's SaveTextSpill.owner session; the backend chooses a private (not world-readable) location and a collision-free name derived from — never equal to — the caller's `suggestedName`. +- `saveText` REJECTS on a real storage failure (permissions, ENOSPC, backend unavailable); the caller decides how to degrade (the spill policy treats a rejection as best-effort and keeps the inline result). + +```ts cordis-catalog +/** + * Persist `input.content` to a session-scoped spill artifact. + * @param input - the owner, provenance, suggested name, and full text to save. + * @returns the saved artifact's {@link SpillRef}; rejects on a storage failure. + */ +abstract saveText(input: SaveTextSpill): Promise +``` + +Source: [`packages/spill/spill/src/index.ts:45`](../../packages/spill/spill/src/index.ts) + diff --git a/docs/subsystems/storage.i18n.yaml b/docs/subsystems/storage.i18n.yaml index 6560eed831..98092af5b3 100644 --- a/docs/subsystems/storage.i18n.yaml +++ b/docs/subsystems/storage.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/storage.md -storage.md: 5d54d7337b5ef52e24179758130b9fa8cd196376 -storage.zh.md: 46346b64351322d40de166a71f7a3204c8902b2d +storage.md: 52dc84b699921e911276ce5214bc975abfd22fa4 +storage.zh.md: f614b77153e835264e1650515946a9d2acf70e57 diff --git a/docs/subsystems/storage.md b/docs/subsystems/storage.md index 5d54d7337b..52dc84b699 100644 --- a/docs/subsystems/storage.md +++ b/docs/subsystems/storage.md @@ -8,7 +8,7 @@ Source: [`packages/storage/storage/src/backend.ts`](../../packages/storage/stora ## The hub: `ctx.storage` -`Storage` ([signatures](../cordis-catalog/services.md#ctxstorage--storage)) is a meeting point, not a store. `ctx.storage.backend` is a name → backend table: multiple backends stay mounted side by side, and which backend serves which consumer is that consumer's configuration (the domain layer's route table), never a hub-global choice. `register(name, backend)` returns the disposer; duplicate names and unknown lookups throw `StorageError`. Disposal only unregisters the name — the owning plugin closes the backend after unregistering. Each backend plugin also publishes a lifecycle-only service key (`storageBackendServiceKey(name)`), which form providers inject so their activation cannot race backend registration. +`Storage` ([signatures](#ctxstorage--storage)) is a meeting point, not a store. `ctx.storage.backend` is a name → backend table: multiple backends stay mounted side by side, and which backend serves which consumer is that consumer's configuration (the domain layer's route table), never a hub-global choice. `register(name, backend)` returns the disposer; duplicate names and unknown lookups throw `StorageError`. Disposal only unregisters the name — the owning plugin closes the backend after unregistering. Each backend plugin also publishes a lifecycle-only service key (`storageBackendServiceKey(name)`), which form providers inject so their activation cannot race backend registration. Data forms mount on the hub under a merge-extensible key map: @@ -99,11 +99,11 @@ Reads are synchronous from authoritative in-memory state: `KvTable` exposes `get ## The domain facility: `ctx.storageDomain` -`DomainFacility` ([signatures](../cordis-catalog/services.md#ctxstoragedomain--domainfacility)) opens declared domains over routed backends. Routing is the domain plugin's configuration, never the hub's: `backend` names the required default route and `routes` overrides it per domain name. `open(spec)` runs a strict sequence, each step failing the whole call: it rejects a name already open or still closing (`already-open`), resolves the route (`backend-not-found`), requires the backend's `kv` facet (`facet-unsupported`), opens the unit (backend `version-mismatch`/`malformed-medium` pass through), and validates every stored record and global against the spec's zod schemas (`invalid-record` with the offending table and key). The caller owns the returned handle and releases it with `Domain.close()`; domains still open when the plugin unmounts are closed by the facility, and a closed domain's name frees for reopening only after teardown fully completes. `get(name)` is an untyped diagnostic lookup onto the package-private `DomainImpl` runtime behind every typed handle; `closeAll()` is the unmount path. +`DomainFacility` ([signatures](#ctxstoragedomain--domainfacility)) opens declared domains over routed backends. Routing is the domain plugin's configuration, never the hub's: `backend` names the required default route and `routes` overrides it per domain name. `open(spec)` runs a strict sequence, each step failing the whole call: it rejects a name already open or still closing (`already-open`), resolves the route (`backend-not-found`), requires the backend's `kv` facet (`facet-unsupported`), opens the unit (backend `version-mismatch`/`malformed-medium` pass through), and validates every stored record and global against the spec's zod schemas (`invalid-record` with the offending table and key). The caller owns the returned handle and releases it with `Domain.close()`; domains still open when the plugin unmounts are closed by the facility, and a closed domain's name frees for reopening only after teardown fully completes. `get(name)` is an untyped diagnostic lookup onto the package-private `DomainImpl` runtime behind every typed handle; `closeAll()` is the unmount path. ## The change event: `domain/changed` -Every durable write emits one event strictly after the backend acknowledged durability, in the domain's write-chain order ([event entry](../cordis-catalog/events.md#domainchanged--emit)): +Every durable write emits one event strictly after the backend acknowledged durability, in the domain's write-chain order ([event entry](#domainchanged--emit)): ```ts type-equiv /** Shared location fields of one durable domain change. */ @@ -123,3 +123,107 @@ type DomainChanged = DomainChangedPut | DomainChangedDeleted ``` `put` (inserts, overwrites, and global writes) carries the new snapshot in `value` — never the old value; a diffing consumer keeps its own previous snapshot. `deleted` is a tombstone with no value. The event is a notification, not a transaction participant: the commit point has passed at emission, so a synchronously throwing listener is contained with a logged warning rather than rejecting the already-durable write, and emitted values equal the in-memory state at emission. The event is in-process only; cross-process change push is deferred work recorded in the [package README](../../packages/storage/storage-domain/README.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.storage` — `Storage` + +The storage hub service. Backends register under `backend`; data forms mount under their `StorageForms` key and are reached as `ctx.storage.`. + +```ts cordis-catalog +/** + * Mount a data-form facility on the hub. Mounting is an effect: the + * returned disposer unmounts the form. + * @param form - Form key declared in {@link StorageForms}. + * @param facility - The facility instance to expose. + * @returns the disposer that unmounts the form. + */ +mount(form: K, facility: StorageForms[K]): () => void + +/** + * Resolve a mounted data form. + * @param form - Form key declared in {@link StorageForms}. + * @returns the mounted facility. + */ +form(form: K): StorageForms[K] +``` + +Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts) + + + +### `ctx.storageDomain` — `DomainFacility` + +The mounted domain facility. Opens declared domains over routed backends; one facility instance owns the open-domain table and enforces single-open per domain name. + +```ts cordis-catalog +/** + * Open one declared domain. Steps, each failing the whole call: reject a + * name that is already open (`already-open`); resolve the backend route + * (`backend-not-found` passes through from the hub); require its `kv` facet + * (`facet-unsupported`); open the unit projected from the spec (backend + * `version-mismatch`/`malformed-medium` pass through); load and validate + * every stored record against the spec's zod schemas (`invalid-record` + * with the offending table and key); construct the domain. + * + * Lifecycle: the CALLER owns the returned handle and closes it via + * `Domain.close()` (typically as its own `ctx.effect` disposer) — the + * facility does not tie the domain to any consumer fiber. Domains still + * open when the facility unmounts are closed by the plugin disposer. + * @param spec - The domain declaration, typically from `defineDomain`. + * @returns the opened domain handle, typed by the spec. + */ +async open(spec: S): Promise> + +/** + * Look up an open domain by name, untyped. Diagnostic surface (the package + * invariant cross-checks change events against live domain state); typed + * consumers hold the handle returned by {@link open}. + * @param name - Domain name. + * @returns the open domain runtime, or `undefined` when not open. + */ +get(name: string): DomainImpl | undefined + +/** + * Close every domain still open on this facility. The unmount path for + * consumers that never called `Domain.close()` themselves; closing is + * idempotent, so double-closing an already-closed domain is harmless. + * @returns resolution after every unit is released. + */ +async closeAll(): Promise +``` + +Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts) + + + +### `domain/*` events + + + +#### `domain/changed` — emit + +A domain record or the global singleton changed, emitted once per write strictly after the backend acknowledged durability. Events of one domain arrive in its write-chain order. + +```ts cordis-catalog +/** + * A domain record or the global singleton changed, emitted once per write + * strictly after the backend acknowledged durability. Events of one + * domain arrive in its write-chain order. + * @param change - domain, table (`''` for global), key (`''` for global), + * operation discriminant, and on `put` the new snapshot. + * @mode emit + */ +'domain/changed'(change: DomainChanged): void +``` + +Source: [`packages/storage/storage-domain/src/events.ts:46`](../../packages/storage/storage-domain/src/events.ts) + diff --git a/docs/subsystems/storage.zh.md b/docs/subsystems/storage.zh.md index 46346b6435..f614b77153 100644 --- a/docs/subsystems/storage.zh.md +++ b/docs/subsystems/storage.zh.md @@ -8,7 +8,7 @@ ## 枢纽:`ctx.storage` -`Storage`([签名](../cordis-catalog/services.md#ctxstorage--storage))是汇合点,不是存储本体。`ctx.storage.backend` 是一张名称 → 后端的表:多个后端并排保持挂载,哪个后端服务哪个消费方由该消费方自己的配置决定(即领域层的路由表),绝不是枢纽全局的选择。`register(name, backend)` 返回 disposer;重复名称与查找未知名称都抛出 `StorageError`。dispose(资源释放)只注销名称——由拥有插件在注销之后自行关闭后端。每个后端插件还会发布一个仅用于生命周期的服务键(`storageBackendServiceKey(name)`),数据形式提供方注入它,使自身激活不会与后端注册发生竞态。 +`Storage`([签名](#ctxstorage--storage))是汇合点,不是存储本体。`ctx.storage.backend` 是一张名称 → 后端的表:多个后端并排保持挂载,哪个后端服务哪个消费方由该消费方自己的配置决定(即领域层的路由表),绝不是枢纽全局的选择。`register(name, backend)` 返回 disposer;重复名称与查找未知名称都抛出 `StorageError`。dispose(资源释放)只注销名称——由拥有插件在注销之后自行关闭后端。每个后端插件还会发布一个仅用于生命周期的服务键(`storageBackendServiceKey(name)`),数据形式提供方注入它,使自身激活不会与后端注册发生竞态。 数据形式以一张可合并扩展的键 map 挂载到枢纽上: @@ -99,11 +99,11 @@ interface Domain { ## 领域 facility:`ctx.storageDomain` -`DomainFacility`([签名](../cordis-catalog/services.md#ctxstoragedomain--domainfacility))在经过路由的后端之上打开已声明的领域。路由是领域插件的配置,绝不属于枢纽:`backend` 指定必填的默认路由,`routes` 按领域名逐个覆盖。`open(spec)` 按严格顺序执行,每一步失败都使整个调用失败:拒绝已打开或仍在关闭中的名称(`already-open`),解析路由(`backend-not-found`),要求后端具备 `kv` facet(`facet-unsupported`),打开 unit(后端的 `version-mismatch`/`malformed-medium` 原样透传),并按 spec 的 zod schema 校验每条已存储记录和 global(`invalid-record`,附带出错的表与键)。调用方拥有返回的句柄,并用 `Domain.close()` 释放它;插件卸载时仍处于打开状态的领域由 facility 负责关闭,已关闭领域的名称只有在拆除完全结束后才释放出来供重新打开。`get(name)` 是无类型的诊断查找,命中的是每个类型化句柄背后包内私有的 `DomainImpl` 运行时;`closeAll()` 是卸载路径。 +`DomainFacility`([签名](#ctxstoragedomain--domainfacility))在经过路由的后端之上打开已声明的领域。路由是领域插件的配置,绝不属于枢纽:`backend` 指定必填的默认路由,`routes` 按领域名逐个覆盖。`open(spec)` 按严格顺序执行,每一步失败都使整个调用失败:拒绝已打开或仍在关闭中的名称(`already-open`),解析路由(`backend-not-found`),要求后端具备 `kv` facet(`facet-unsupported`),打开 unit(后端的 `version-mismatch`/`malformed-medium` 原样透传),并按 spec 的 zod schema 校验每条已存储记录和 global(`invalid-record`,附带出错的表与键)。调用方拥有返回的句柄,并用 `Domain.close()` 释放它;插件卸载时仍处于打开状态的领域由 facility 负责关闭,已关闭领域的名称只有在拆除完全结束后才释放出来供重新打开。`get(name)` 是无类型的诊断查找,命中的是每个类型化句柄背后包内私有的 `DomainImpl` 运行时;`closeAll()` 是卸载路径。 ## 变更事件:`domain/changed` -每次持久写入都发出一个事件,严格发生在后端确认持久性之后,顺序遵循该领域的写链([事件条目](../cordis-catalog/events.md#domainchanged--emit)): +每次持久写入都发出一个事件,严格发生在后端确认持久性之后,顺序遵循该领域的写链([事件条目](#domainchanged--emit)): ```ts type-equiv /** Shared location fields of one durable domain change. */ @@ -123,3 +123,107 @@ type DomainChanged = DomainChangedPut | DomainChangedDeleted ``` `put`(插入、覆写和 global 写入)在 `value` 中携带新快照——绝不携带旧值;需要做差异比较的消费方自行保留上一份快照。`deleted` 是不携带值的墓碑。该事件是通知,不是事务参与者:发出时提交点已经过去,因此同步抛出的监听器会被兜住并记录一条警告,而不会让已经持久的写入被拒绝;发出的值等于发出时刻的内存态。该事件仅限进程内;跨进程的变更推送是延后工作,记录在[包 README](../../packages/storage/storage-domain/README.md)中。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.storage` — `Storage` + +The storage hub service. Backends register under `backend`; data forms mount under their `StorageForms` key and are reached as `ctx.storage.`. + +```ts cordis-catalog +/** + * Mount a data-form facility on the hub. Mounting is an effect: the + * returned disposer unmounts the form. + * @param form - Form key declared in {@link StorageForms}. + * @param facility - The facility instance to expose. + * @returns the disposer that unmounts the form. + */ +mount(form: K, facility: StorageForms[K]): () => void + +/** + * Resolve a mounted data form. + * @param form - Form key declared in {@link StorageForms}. + * @returns the mounted facility. + */ +form(form: K): StorageForms[K] +``` + +Source: [`packages/storage/storage/src/index.ts:47`](../../packages/storage/storage/src/index.ts) + + + +### `ctx.storageDomain` — `DomainFacility` + +The mounted domain facility. Opens declared domains over routed backends; one facility instance owns the open-domain table and enforces single-open per domain name. + +```ts cordis-catalog +/** + * Open one declared domain. Steps, each failing the whole call: reject a + * name that is already open (`already-open`); resolve the backend route + * (`backend-not-found` passes through from the hub); require its `kv` facet + * (`facet-unsupported`); open the unit projected from the spec (backend + * `version-mismatch`/`malformed-medium` pass through); load and validate + * every stored record against the spec's zod schemas (`invalid-record` + * with the offending table and key); construct the domain. + * + * Lifecycle: the CALLER owns the returned handle and closes it via + * `Domain.close()` (typically as its own `ctx.effect` disposer) — the + * facility does not tie the domain to any consumer fiber. Domains still + * open when the facility unmounts are closed by the plugin disposer. + * @param spec - The domain declaration, typically from `defineDomain`. + * @returns the opened domain handle, typed by the spec. + */ +async open(spec: S): Promise> + +/** + * Look up an open domain by name, untyped. Diagnostic surface (the package + * invariant cross-checks change events against live domain state); typed + * consumers hold the handle returned by {@link open}. + * @param name - Domain name. + * @returns the open domain runtime, or `undefined` when not open. + */ +get(name: string): DomainImpl | undefined + +/** + * Close every domain still open on this facility. The unmount path for + * consumers that never called `Domain.close()` themselves; closing is + * idempotent, so double-closing an already-closed domain is harmless. + * @returns resolution after every unit is released. + */ +async closeAll(): Promise +``` + +Source: [`packages/storage/storage-domain/src/index.ts:69`](../../packages/storage/storage-domain/src/index.ts) + + + +### `domain/*` events + + + +#### `domain/changed` — emit + +A domain record or the global singleton changed, emitted once per write strictly after the backend acknowledged durability. Events of one domain arrive in its write-chain order. + +```ts cordis-catalog +/** + * A domain record or the global singleton changed, emitted once per write + * strictly after the backend acknowledged durability. Events of one + * domain arrive in its write-chain order. + * @param change - domain, table (`''` for global), key (`''` for global), + * operation discriminant, and on `put` the new snapshot. + * @mode emit + */ +'domain/changed'(change: DomainChanged): void +``` + +Source: [`packages/storage/storage-domain/src/events.ts:46`](../../packages/storage/storage-domain/src/events.ts) + diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index 57b960da91..e6ead80f87 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/subagent.md -subagent.md: b26a12d1d50305d86d7ada29cac83474009d81ce -subagent.zh.md: 6c4c64ff22050b73699a97093acd0032668fde3d +# pnpm run verify-translation-pairing --write docs/subsystems/subagent.md +subagent.md: cbf9043b36a5cb2ab37bb99c6dbbf5a18d1e0288 +subagent.zh.md: ffd8fefd8b9d128c21e1b91ee0ba1fb706638fa9 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index b26a12d1d5..cbf9043b36 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -433,3 +433,269 @@ The spawn and fork backends create an ordinary one-shot agent through `parent.ct - **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap. - **Fork seeding** uses `CreateAgentOptions.seed` (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.subagents` — `SubagentService` + +Named provider registry with one-shot runs, durable discovery, and continuable-child operations. + +```ts cordis-catalog +/** + * Establish one durable continuable child and deliver its initial prompt. + * Resolves when the child's inbox accepts that prompt, without waiting for the + * turn to start or for the message to reach the Session log; any earlier + * failure rejects with no ids and rolls back the child entirely. + * @param spec - provider, delegation request, and caller cancellation. + * @returns the durable child id and the accepted prompt's message id. + * @throws when continuation services are unavailable or materialization fails. + */ +async startContinuable(spec: ContinuableStartSpec): Promise + +/** + * Deliver one later message to a continuable child as its next FIFO turn. A + * resident child's Agent inbox accepts it directly (waking a `waiting` + * Activation), while an absent one is cold-resumed from its persisted + * Session. The Agent inbox is the only queue, so every accepted message has + * one observable order. + * @param parent - the exact live direct parent authorizing this delivery. + * @param childId - durable child session id. + * @param content - user-role content to deliver. + * @param options - durable provenance and caller cancellation, which stops the + * operation only before inbox acceptance. + * @returns the accepted message's inbox id. + * @throws when continuation services are unavailable, parent authority is + * rejected, or the message was not admitted. + */ +async followup( parent: Agent, childId: SessionId, content: ContentBlock[], options: SubagentFollowupOptions, ): Promise + +/** + * Interrupt one live continuable child's current turn under a human parent + * address or an exact live ancestor Agent. Fire-and-return: the cancel + * signal is issued before this returns, but the target may keep running + * until it observes the signal. Unclaimed pending inbox work, the Activation, + * and published descendants are preserved; claimed work is not requeued. + * Once the interrupted driver is idle, a waking send resumes the parked FIFO + * queue. An absent target — including a one-shot or unknown id — + * is an accepted no-op, as is a manager-less composition, which cannot own a + * live Activation. + * @param targetSessionId - the durable child session id to interrupt. + * @param authority - the human parent address or exact live ancestor Agent. + * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the + * live target. + */ +interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void + +/** + * Deliver selected content from one live continuable child to its durable + * direct parent. The child is the authority credential; callers cannot name a + * recipient. Reporting does not conclude the child's turn or Activation. + * @param child - exact live reporting child. + * @param content - selected model-facing content. + * @param options - parent scheduling and pre-acceptance cancellation. + * @returns the stable identity of the parent-accepted message. + * @throws when continuation services are unavailable, sender authorization + * fails, or the direct parent is not live. + */ +async reportFrom( child: Agent, content: ContentBlock[], options: SubagentReportOptions, ): Promise + +/** + * Compose one deployment capability into every continuable child's + * unpublished creation context on fresh creation and cold resume. Grants wait + * for the next Activation; removing the contribution revokes every resident + * installation immediately. + * @param contribution - synchronous child-scope installer. + * @returns the exact Cordis effect disposer. + */ +registerContinuableSetup(contribution: ContinuableSetupContribution): () => void + +/** + * Close continuable admission below exact live parent Agents, stop only their + * visible descendant Activations synchronously, then await admitted scoped + * materializations and release those forests child-first. The scoped cutoff + * lasts until each exact parent leaves the registry; unrelated parent trees + * remain live. + * @param parents - exact host-owned parent Agents entering teardown. + * @returns once every retained descendant Activation released its `AgentHandle`. + * @throws an aggregate error after all branches settle when any failed. + */ +async drainContinuableDescendants(parents: readonly Agent[]): Promise + +/** + * Enumerate the parent's direct session-backed subagents without loading or + * resuming an Agent and without any query seam: the listing merges the live + * session store with optional session persistence (live-preferred) and + * serves each child's durable mode/label from the registered `subagent` + * projection unit down a three-rung ladder — the registry's watermark + * snapshot for a live child; for a cold one, a durable projection-cache + * row when the optional cache serves an own-suffix identity (its `seq` + * gate proves the value postdates the fork seed, where a child's own + * descriptor is immutable once appended), else one persistence inspection + * folded through the registry. The + * projection fold is the single classification authority; per-child + * diagnostics relay a fold that served no identity or a failed inspection, + * never a list-time descriptor parse. Absent persistence, enumeration is + * live-only (a cold child cannot be resumed then either, so its absence is + * capability absence, not an error). This service consults no Agent + * registrations, Activations, or providers. + * + * Every persistence read receives `signal`, and the listing rechecks + * cancellation around each of those awaits. Read rejections that settle + * after an abort become a stable `SubagentError` with code `CANCELLED`. + * @param parentSessionId - parent session whose direct children are listed. + * @param signal - caller-owned cancellation forwarded to persistence reads + * and observed around every read await. + * @returns children and per-child diagnostics ordered by `createdAt`, then id. + * @throws {@link SubagentError} when the projection registry or the session + * store is not mounted, or the caller cancels the listing. + */ +listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Enumerate the root's complete session-backed subagent tree in stable + * pre-order from one live-preferred corpus, without loading or resuming an + * Agent. Ordinary sessions and one-shot children remain traversal nodes so + * continuable descendants below them are discovered; each returned entry + * adds its durable `parentId` and root-relative `depth`. Identity resolution, + * diagnostics, optional persistence, and cancellation follow the same + * projection-backed contract as {@link listChildren}. + * @param rootSessionId - session whose complete descendant tree is listed. + * @param signal - caller-owned cancellation forwarded to persistence reads + * and observed around every read await. + * @returns children and per-candidate diagnostics with tree position, in + * stable pre-order. + * @throws {@link SubagentError} under the same conditions as {@link listChildren}. + */ +listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Register a provider under its name. Registration is effect-scoped and HMR + * safe; removing a provider blocks new starts but does not revoke runs that + * were already returned to their holders. + * @param provider - the trusted provider implementation. + * @returns the exact Cordis effect disposer. + */ +registerProvider(provider: SubagentProvider): () => void + +/** + * Look up a provider by name. + * @param name - the provider name. + * @returns the provider, or undefined when absent. + */ +getProvider(name: string): SubagentProvider | undefined + +/** + * List registered provider names in insertion order. + * @returns the registered names. + */ +list(): string[] + +/** + * Establish a published child on the named provider. Capability and semantic + * checks run before delegation. Provider ownership lasts until its promise + * fulfills; a rejection therefore has no run for the caller to dispose and + * emits no run lifecycle events. Post-publication turn and infrastructure + * failures settle through the returned run. + * @param name - the provider to use. + * @param request - child label, prompt, parent, signal, and optional capabilities. + * @returns the published holder-owned run. + */ +async start(name: string, request: SubagentStartRequest): Promise +``` + +Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) + +Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) + + + +### `subagent/*` events + + + +#### `subagent/end` — emit + +A published child settled. Scope-filtered dispatch uses the same delegating parent carrier as `subagent/start`, so the lifecycle pair reaches the same scoped audience. + +```ts cordis-catalog +/** + * A published child settled. Scope-filtered dispatch uses the same delegating + * parent carrier as `subagent/start`, so the lifecycle pair reaches the + * same scoped audience. + * @param info - the run identity and terminal outcome. + * @dshScopeScan unsupported + * @mode emit + */ +'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/provider-added` — emit + +A provider became resolvable in the registry. + +```ts cordis-catalog +/** + * A provider became resolvable in the registry. + * @param provider - the registered provider. + * @mode emit + */ +'subagent/provider-added'(provider: SubagentProvider): void +``` + +Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/provider-removed` — emit + +A provider left the registry. Accepted runs remain holder-owned. + +```ts cordis-catalog +/** + * A provider left the registry. Accepted runs remain holder-owned. + * @param name - the provider name that no longer resolves. + * @mode emit + */ +'subagent/provider-removed'(name: string): void +``` + +Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/start` — emit + +A provider established a published child. For in-process providers, `ctx.agents.get(info.id)` resolves during this notification. Scope-filtered dispatch keys the carrier by the delegating parent, so a parent-scoped listener observes only its own delegations. Paired with `subagent/end`. + +```ts cordis-catalog +/** + * A provider established a published child. For in-process providers, + * `ctx.agents.get(info.id)` resolves during this notification. + * Scope-filtered dispatch keys the carrier by the delegating parent, so a + * parent-scoped listener observes only its own delegations. Paired with + * `subagent/end`. + * @param info - the provider and published child identity. + * @dshScopeScan unsupported + * @mode emit + */ +'subagent/start'(this: Scoped, info: SubagentRunInfo): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts) + diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index 6c4c64ff22..ffd8fefd8b 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -435,3 +435,269 @@ spawn 和 fork 后端通过 `parent.ctx` 创建一个普通的单次 agent,将 - **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,冷恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。 - **Fork 种子注入**使用 `CreateAgentOptions.seed`(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/support/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.subagents` — `SubagentService` + +Named provider registry with one-shot runs, durable discovery, and continuable-child operations. + +```ts cordis-catalog +/** + * Establish one durable continuable child and deliver its initial prompt. + * Resolves when the child's inbox accepts that prompt, without waiting for the + * turn to start or for the message to reach the Session log; any earlier + * failure rejects with no ids and rolls back the child entirely. + * @param spec - provider, delegation request, and caller cancellation. + * @returns the durable child id and the accepted prompt's message id. + * @throws when continuation services are unavailable or materialization fails. + */ +async startContinuable(spec: ContinuableStartSpec): Promise + +/** + * Deliver one later message to a continuable child as its next FIFO turn. A + * resident child's Agent inbox accepts it directly (waking a `waiting` + * Activation), while an absent one is cold-resumed from its persisted + * Session. The Agent inbox is the only queue, so every accepted message has + * one observable order. + * @param parent - the exact live direct parent authorizing this delivery. + * @param childId - durable child session id. + * @param content - user-role content to deliver. + * @param options - durable provenance and caller cancellation, which stops the + * operation only before inbox acceptance. + * @returns the accepted message's inbox id. + * @throws when continuation services are unavailable, parent authority is + * rejected, or the message was not admitted. + */ +async followup( parent: Agent, childId: SessionId, content: ContentBlock[], options: SubagentFollowupOptions, ): Promise + +/** + * Interrupt one live continuable child's current turn under a human parent + * address or an exact live ancestor Agent. Fire-and-return: the cancel + * signal is issued before this returns, but the target may keep running + * until it observes the signal. Unclaimed pending inbox work, the Activation, + * and published descendants are preserved; claimed work is not requeued. + * Once the interrupted driver is idle, a waking send resumes the parked FIFO + * queue. An absent target — including a one-shot or unknown id — + * is an accepted no-op, as is a manager-less composition, which cannot own a + * live Activation. + * @param targetSessionId - the durable child session id to interrupt. + * @param authority - the human parent address or exact live ancestor Agent. + * @throws {SubagentError} `UNAUTHORIZED` when the authority does not own the + * live target. + */ +interrupt(targetSessionId: SessionId, authority: SubagentInterruptAuthority): void + +/** + * Deliver selected content from one live continuable child to its durable + * direct parent. The child is the authority credential; callers cannot name a + * recipient. Reporting does not conclude the child's turn or Activation. + * @param child - exact live reporting child. + * @param content - selected model-facing content. + * @param options - parent scheduling and pre-acceptance cancellation. + * @returns the stable identity of the parent-accepted message. + * @throws when continuation services are unavailable, sender authorization + * fails, or the direct parent is not live. + */ +async reportFrom( child: Agent, content: ContentBlock[], options: SubagentReportOptions, ): Promise + +/** + * Compose one deployment capability into every continuable child's + * unpublished creation context on fresh creation and cold resume. Grants wait + * for the next Activation; removing the contribution revokes every resident + * installation immediately. + * @param contribution - synchronous child-scope installer. + * @returns the exact Cordis effect disposer. + */ +registerContinuableSetup(contribution: ContinuableSetupContribution): () => void + +/** + * Close continuable admission below exact live parent Agents, stop only their + * visible descendant Activations synchronously, then await admitted scoped + * materializations and release those forests child-first. The scoped cutoff + * lasts until each exact parent leaves the registry; unrelated parent trees + * remain live. + * @param parents - exact host-owned parent Agents entering teardown. + * @returns once every retained descendant Activation released its `AgentHandle`. + * @throws an aggregate error after all branches settle when any failed. + */ +async drainContinuableDescendants(parents: readonly Agent[]): Promise + +/** + * Enumerate the parent's direct session-backed subagents without loading or + * resuming an Agent and without any query seam: the listing merges the live + * session store with optional session persistence (live-preferred) and + * serves each child's durable mode/label from the registered `subagent` + * projection unit down a three-rung ladder — the registry's watermark + * snapshot for a live child; for a cold one, a durable projection-cache + * row when the optional cache serves an own-suffix identity (its `seq` + * gate proves the value postdates the fork seed, where a child's own + * descriptor is immutable once appended), else one persistence inspection + * folded through the registry. The + * projection fold is the single classification authority; per-child + * diagnostics relay a fold that served no identity or a failed inspection, + * never a list-time descriptor parse. Absent persistence, enumeration is + * live-only (a cold child cannot be resumed then either, so its absence is + * capability absence, not an error). This service consults no Agent + * registrations, Activations, or providers. + * + * Every persistence read receives `signal`, and the listing rechecks + * cancellation around each of those awaits. Read rejections that settle + * after an abort become a stable `SubagentError` with code `CANCELLED`. + * @param parentSessionId - parent session whose direct children are listed. + * @param signal - caller-owned cancellation forwarded to persistence reads + * and observed around every read await. + * @returns children and per-child diagnostics ordered by `createdAt`, then id. + * @throws {@link SubagentError} when the projection registry or the session + * store is not mounted, or the caller cancels the listing. + */ +listChildren(parentSessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Enumerate the root's complete session-backed subagent tree in stable + * pre-order from one live-preferred corpus, without loading or resuming an + * Agent. Ordinary sessions and one-shot children remain traversal nodes so + * continuable descendants below them are discovered; each returned entry + * adds its durable `parentId` and root-relative `depth`. Identity resolution, + * diagnostics, optional persistence, and cancellation follow the same + * projection-backed contract as {@link listChildren}. + * @param rootSessionId - session whose complete descendant tree is listed. + * @param signal - caller-owned cancellation forwarded to persistence reads + * and observed around every read await. + * @returns children and per-candidate diagnostics with tree position, in + * stable pre-order. + * @throws {@link SubagentError} under the same conditions as {@link listChildren}. + */ +listDescendants(rootSessionId: SessionId, signal?: AbortSignal): Promise + +/** + * Register a provider under its name. Registration is effect-scoped and HMR + * safe; removing a provider blocks new starts but does not revoke runs that + * were already returned to their holders. + * @param provider - the trusted provider implementation. + * @returns the exact Cordis effect disposer. + */ +registerProvider(provider: SubagentProvider): () => void + +/** + * Look up a provider by name. + * @param name - the provider name. + * @returns the provider, or undefined when absent. + */ +getProvider(name: string): SubagentProvider | undefined + +/** + * List registered provider names in insertion order. + * @returns the registered names. + */ +list(): string[] + +/** + * Establish a published child on the named provider. Capability and semantic + * checks run before delegation. Provider ownership lasts until its promise + * fulfills; a rejection therefore has no run for the caller to dispose and + * emits no run lifecycle events. Post-publication turn and infrastructure + * failures settle through the returned run. + * @param name - the provider to use. + * @param request - child label, prompt, parent, signal, and optional capabilities. + * @returns the published holder-owned run. + */ +async start(name: string, request: SubagentStartRequest): Promise +``` + +Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) + +Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) + + + +### `subagent/*` events + + + +#### `subagent/end` — emit + +A published child settled. Scope-filtered dispatch uses the same delegating parent carrier as `subagent/start`, so the lifecycle pair reaches the same scoped audience. + +```ts cordis-catalog +/** + * A published child settled. Scope-filtered dispatch uses the same delegating + * parent carrier as `subagent/start`, so the lifecycle pair reaches the + * same scoped audience. + * @param info - the run identity and terminal outcome. + * @dshScopeScan unsupported + * @mode emit + */ +'subagent/end'(this: Scoped, info: SubagentRunEndInfo): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/subagent/subagent/src/index.ts:162`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/provider-added` — emit + +A provider became resolvable in the registry. + +```ts cordis-catalog +/** + * A provider became resolvable in the registry. + * @param provider - the registered provider. + * @mode emit + */ +'subagent/provider-added'(provider: SubagentProvider): void +``` + +Source: [`packages/subagent/subagent/src/index.ts:136`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/provider-removed` — emit + +A provider left the registry. Accepted runs remain holder-owned. + +```ts cordis-catalog +/** + * A provider left the registry. Accepted runs remain holder-owned. + * @param name - the provider name that no longer resolves. + * @mode emit + */ +'subagent/provider-removed'(name: string): void +``` + +Source: [`packages/subagent/subagent/src/index.ts:142`](../../packages/subagent/subagent/src/index.ts) + + + +#### `subagent/start` — emit + +A provider established a published child. For in-process providers, `ctx.agents.get(info.id)` resolves during this notification. Scope-filtered dispatch keys the carrier by the delegating parent, so a parent-scoped listener observes only its own delegations. Paired with `subagent/end`. + +```ts cordis-catalog +/** + * A provider established a published child. For in-process providers, + * `ctx.agents.get(info.id)` resolves during this notification. + * Scope-filtered dispatch keys the carrier by the delegating parent, so a + * parent-scoped listener observes only its own delegations. Paired with + * `subagent/end`. + * @param info - the provider and published child identity. + * @dshScopeScan unsupported + * @mode emit + */ +'subagent/start'(this: Scoped, info: SubagentRunInfo): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/subagent/subagent/src/index.ts:153`](../../packages/subagent/subagent/src/index.ts) + diff --git a/docs/subsystems/subprocess.i18n.yaml b/docs/subsystems/subprocess.i18n.yaml index b2d38854b2..a3071e98a9 100644 --- a/docs/subsystems/subprocess.i18n.yaml +++ b/docs/subsystems/subprocess.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/subprocess.md -subprocess.md: 023b122218ad1caa2b8e16c26b0bc8b0d4183c28 -subprocess.zh.md: 5ee9c782c0ce73ca9a2e694450ed38b8d548dc09 +# pnpm run verify-translation-pairing --write docs/subsystems/subprocess.md +subprocess.md: 0bc2bf86518bcc60bef02554b94471425f2ac433 +subprocess.zh.md: 5d6425707edcf1952c25dbdc91dbf7aab3779350 diff --git a/docs/subsystems/subprocess.md b/docs/subsystems/subprocess.md index 023b122218..0bc2bf8651 100644 --- a/docs/subsystems/subprocess.md +++ b/docs/subsystems/subprocess.md @@ -242,8 +242,83 @@ interface SubprocessOutcome { `spawnTerminal(spec)` is the non-pipe process primitive. The provider allocates the controlling terminal and owns UTF-8 text transport, foreground-process-group inspection and signalling, and one awaited TERM-to-KILL operation that reaches quiescence for every session member the provider can still observe; providers document substrate-specific observability limits. The PTY backend remains responsible for prompt detection, readiness inference, scrollback, sandbox policy, and persistent-session ownership; ordinary `spawn()` cannot reconstruct controlling-terminal semantics. -The terminal spec fully specifies argv, cwd, environment overrides, dimensions, cleanup grace, and optional allocation cancellation. Its handle exposes `pid`, ordered output, `done`, `write`, `inspectForeground`, `signalForeground`, and awaited `terminate`; the exact public shapes are generated into the [`ctx.subprocess` service catalog](../cordis-catalog/services.md#ctxsubprocess--subprocessservice-abstract-seam). +The terminal spec fully specifies argv, cwd, environment overrides, dimensions, cleanup grace, and optional allocation cancellation. Its handle exposes `pid`, ordered output, `done`, `write`, `inspectForeground`, `signalForeground`, and awaited `terminate`; the exact public shapes are generated into the [`ctx.subprocess` service catalog](#ctxsubprocess--subprocessservice-abstract-seam). ## Service behavior The abstract [`SubprocessService`](../../packages/subprocess/subprocess/src/index.ts) seam defines execution-world coordinates, executable lookup, ordinary `spawn`, and `spawnTerminal`. [`LocalSubprocessService`](../../packages/subprocess/subprocess-local/src/index.ts) implements them with detached process trees, per-disposition wiring, credential scrubbing, `node-pty`, platform process inspection, and terminate-and-join disposal. See [`dsh-subprocess`](../../packages/subprocess/subprocess/README.md) for the interface contract and [`dsh-subprocess-local`](../../packages/subprocess/subprocess-local/README.md) for local mechanics. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.e2b` — `E2BSandboxService` + +Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation. + +```ts cordis-catalog +/** + * Return the shared live SDK handle. + * @returns the created sandbox after the configured cwd exists. + * @throws when E2B rejects creation or the service is disposing. + */ +async getSandbox(): Promise +``` + +Source: [`packages/e2b/e2b/src/index.ts:74`](../../packages/e2b/e2b/src/index.ts) + + + +### `ctx.subprocess` — `SubprocessService` (abstract seam) + +Abstract subprocess service. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.subprocess` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- Executable paths belong to one execution world shared with the mounted filesystem provider. +- spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures. +- Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists. Piped streams are handed to the caller raw and never buffered here. +- SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence. +- Disposal of the service terminates all still-running managed processes and awaits their exit. +- spawnTerminal owns terminal allocation, text transport, foreground groups, signalling, and whole-session quiescence behind one awaited termination method; readiness and persistent-shell policy stay in the PTY consumer. Its output stream ends after queued terminal output when the top-level process exits. + +```ts cordis-catalog +/** + * Resolve one configured executable in this provider's execution world. + * Absolute paths are verified; bare names use the provider's scrubbed PATH + * plus explicit environment overrides. Relative paths containing separators + * are rejected: no current consumer defines which directory they would + * resolve against, so providers fail loud instead of guessing. + * @param command - absolute executable path or bare PATH name. + * @param env - explicit environment entries used for lookup. + * @param signal - aborts remote or local lookup. + * @returns a canonical executable path. + */ +abstract resolveExecutable( command: string, env?: Readonly>, signal?: AbortSignal, ): Promise + +/** + * Start one managed child process from a fully-specified spec; this seam + * applies no defaults. + * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment. + * @returns the live process handle (streams/readers, signalling, outcome promise). + */ +abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle + +/** + * Allocate a real terminal and start one owned process session. This is the + * only non-pipe process primitive: implementations own terminal byte I/O, + * foreground groups, signals, and complete session-tree cleanup. + * @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation. + * @returns the live terminal handle after allocation succeeds. + */ +abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise +``` + +Source: [`packages/subprocess/subprocess/src/index.ts:102`](../../packages/subprocess/subprocess/src/index.ts) + diff --git a/docs/subsystems/subprocess.zh.md b/docs/subsystems/subprocess.zh.md index 5ee9c782c0..5d6425707e 100644 --- a/docs/subsystems/subprocess.zh.md +++ b/docs/subsystems/subprocess.zh.md @@ -242,8 +242,83 @@ interface SubprocessOutcome { `spawnTerminal(spec)` 是非管道进程原语。提供方分配控制终端,并负责 UTF-8 文本传输、前台进程组检查与信号发送,以及一项须等待的 TERM→KILL 操作;该操作会使提供方仍可观察到的每个会话成员完全停稳,提供方则会记录执行基底特有的可观察性限制。PTY 后端仍负责提示符检测、就绪推断、scrollback、沙箱策略和持久会话所有权;普通 `spawn()` 无法重建控制终端语义。 -终端 spec 完全指定 argv、cwd、环境覆盖、尺寸、清理宽限期与可选的分配取消。其句柄公开 `pid`、有序输出、`done`、`write`、`inspectForeground`、`signalForeground` 和须等待的 `terminate`;确切的公共形状生成到 [`ctx.subprocess` 服务目录](../cordis-catalog/services.md#ctxsubprocess--subprocessservice-abstract-seam)中。 +终端 spec 完全指定 argv、cwd、环境覆盖、尺寸、清理宽限期与可选的分配取消。其句柄公开 `pid`、有序输出、`done`、`write`、`inspectForeground`、`signalForeground` 和须等待的 `terminate`;确切的公共形状生成到 [`ctx.subprocess` 服务目录](#ctxsubprocess--subprocessservice-abstract-seam)中。 ## 服务行为 抽象的 [`SubprocessService`](../../packages/subprocess/subprocess/src/index.ts) seam 定义执行世界坐标、可执行文件查找、普通 `spawn` 与 `spawnTerminal`。[`LocalSubprocessService`](../../packages/subprocess/subprocess-local/src/index.ts) 以 detached 进程树、按处置方式接线、凭据清除、`node-pty`、平台进程检查,以及先终止再等待退出的资源释放实现这些能力。接口契约见 [`dsh-subprocess`](../../packages/subprocess/subprocess/README.md),本地机制见 [`dsh-subprocess-local`](../../packages/subprocess/subprocess-local/README.md)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.e2b` — `E2BSandboxService` + +Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation. + +```ts cordis-catalog +/** + * Return the shared live SDK handle. + * @returns the created sandbox after the configured cwd exists. + * @throws when E2B rejects creation or the service is disposing. + */ +async getSandbox(): Promise +``` + +Source: [`packages/e2b/e2b/src/index.ts:74`](../../packages/e2b/e2b/src/index.ts) + + + +### `ctx.subprocess` — `SubprocessService` (abstract seam) + +Abstract subprocess service. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.subprocess` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- Executable paths belong to one execution world shared with the mounted filesystem provider. +- spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures. +- Collect-mode readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists. Piped streams are handed to the caller raw and never buffered here. +- SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence. +- Disposal of the service terminates all still-running managed processes and awaits their exit. +- spawnTerminal owns terminal allocation, text transport, foreground groups, signalling, and whole-session quiescence behind one awaited termination method; readiness and persistent-shell policy stay in the PTY consumer. Its output stream ends after queued terminal output when the top-level process exits. + +```ts cordis-catalog +/** + * Resolve one configured executable in this provider's execution world. + * Absolute paths are verified; bare names use the provider's scrubbed PATH + * plus explicit environment overrides. Relative paths containing separators + * are rejected: no current consumer defines which directory they would + * resolve against, so providers fail loud instead of guessing. + * @param command - absolute executable path or bare PATH name. + * @param env - explicit environment entries used for lookup. + * @param signal - aborts remote or local lookup. + * @returns a canonical executable path. + */ +abstract resolveExecutable( command: string, env?: Readonly>, signal?: AbortSignal, ): Promise + +/** + * Start one managed child process from a fully-specified spec; this seam + * applies no defaults. + * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment. + * @returns the live process handle (streams/readers, signalling, outcome promise). + */ +abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle + +/** + * Allocate a real terminal and start one owned process session. This is the + * only non-pipe process primitive: implementations own terminal byte I/O, + * foreground groups, signals, and complete session-tree cleanup. + * @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation. + * @returns the live terminal handle after allocation succeeds. + */ +abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise +``` + +Source: [`packages/subprocess/subprocess/src/index.ts:102`](../../packages/subprocess/subprocess/src/index.ts) + diff --git a/docs/subsystems/system-prompt.i18n.yaml b/docs/subsystems/system-prompt.i18n.yaml index 5c39e3cbcb..a1e14385f4 100644 --- a/docs/subsystems/system-prompt.i18n.yaml +++ b/docs/subsystems/system-prompt.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/system-prompt.md -system-prompt.md: 59193c1881abcadbc8a1778cde92f5a6572eee24 -system-prompt.zh.md: 41e45417817895ccf6def70e510eca7422a65e41 +system-prompt.md: 94ce40f8bf98dd4efe3514879c2527c2a7bd3b21 +system-prompt.zh.md: 37d366774089f91756433602a719e99b57747c17 diff --git a/docs/subsystems/system-prompt.md b/docs/subsystems/system-prompt.md index 59193c1881..94ce40f8bf 100644 --- a/docs/subsystems/system-prompt.md +++ b/docs/subsystems/system-prompt.md @@ -76,3 +76,113 @@ interface PromptContext { readonly text: string | ((context: AssembleContext) => string) } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.systemPrompt` — `SystemPrompt` + +Registry service for the prompt inputs assembled before each model step. + +```ts cordis-catalog +/** + * Register an ordered prompt section in the calling context's scope. A scoped + * section shadows a global section with the same name; duplicates within one + * layer and non-finite orders throw. Registration and disposal emit + * `system-prompt/change`. + * @param section - the section to register. + * @returns the exact Cordis effect disposer. + */ +section(section: PromptSection): () => void + +/** + * Register ordered dynamic context in the calling context's scope. Scoped + * entries shadow global entries with the same name. + * @param context - the context contribution to register. + * @returns the exact Cordis effect disposer. + */ +context(context: PromptContext): () => void + +/** + * Register a tool-schema provider in the calling context's scope. Global and + * matching scoped providers both contribute; returning the reserved + * {@link TOOL_ORDER_REST} name makes assembly fail. + * @param provider - evaluated for each assembly with its context. + * @returns the exact Cordis effect disposer. + */ +tools(provider: (context: AssembleContext) => ToolProviderResult): () => void + +/** + * Register a prompt variable in the calling context's scope. Scoped values + * shadow globals; invalid or duplicate names throw. A provider may return + * `undefined`, but rendering a section that references that value then fails. + * @param name - the `[a-z][a-z0-9_]*` reference name. + * @param provider - evaluated for each assembly. + * @returns the exact Cordis effect disposer. + */ +variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void + +/** + * Assemble global and scoped providers, detach tool parameters, apply + * canonical ordering, then run the assembly waterfall. Scoped sections and + * variables shadow globals; the returned waterfall value is authoritative. + * @param context - the optional scope and plugin-defined assembly fields. + * @returns the authoritative post-waterfall assembly. + */ +async assemble(context: AssembleContext = {}): Promise +``` + +Source: [`packages/core/system-prompt/src/index.ts:314`](../../packages/core/system-prompt/src/index.ts) + + + +### `system-prompt/*` events + + + +#### `system-prompt/assemble` — waterfall + +Expert waterfall over the assembled sections, contexts, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative. A supplied signal controls only this explicit assembly request and must not be retained to control later turns. + +```ts cordis-catalog +/** + * Expert waterfall over the assembled sections, contexts, tools, and variables. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners + * receive only that scope's assemblies. The returned value is authoritative. + * A supplied signal controls only this explicit assembly request and must not + * be retained to control later turns. + * @param assembly - the mutable assembly built from registered providers. + * @param context - the caller's per-assembly context. + * @mode waterfall + */ +'system-prompt/assemble'(this: Scoped, assembly: PromptAssembly, context: AssembleContext, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/system-prompt/src/index.ts:29`](../../packages/core/system-prompt/src/index.ts) + + + +#### `system-prompt/change` — emit + +Emitted when any prompt provider changes. This registry notification is unfiltered because a global change affects every scope. + +```ts cordis-catalog +/** + * Emitted when any prompt provider changes. This registry notification is + * unfiltered because a global change affects every scope. + * @mode emit + */ +'system-prompt/change'(): void +``` + +Source: [`packages/core/system-prompt/src/index.ts:35`](../../packages/core/system-prompt/src/index.ts) + diff --git a/docs/subsystems/system-prompt.zh.md b/docs/subsystems/system-prompt.zh.md index 41e4541781..37d3667740 100644 --- a/docs/subsystems/system-prompt.zh.md +++ b/docs/subsystems/system-prompt.zh.md @@ -76,3 +76,113 @@ interface PromptContext { readonly text: string | ((context: AssembleContext) => string) } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.systemPrompt` — `SystemPrompt` + +Registry service for the prompt inputs assembled before each model step. + +```ts cordis-catalog +/** + * Register an ordered prompt section in the calling context's scope. A scoped + * section shadows a global section with the same name; duplicates within one + * layer and non-finite orders throw. Registration and disposal emit + * `system-prompt/change`. + * @param section - the section to register. + * @returns the exact Cordis effect disposer. + */ +section(section: PromptSection): () => void + +/** + * Register ordered dynamic context in the calling context's scope. Scoped + * entries shadow global entries with the same name. + * @param context - the context contribution to register. + * @returns the exact Cordis effect disposer. + */ +context(context: PromptContext): () => void + +/** + * Register a tool-schema provider in the calling context's scope. Global and + * matching scoped providers both contribute; returning the reserved + * {@link TOOL_ORDER_REST} name makes assembly fail. + * @param provider - evaluated for each assembly with its context. + * @returns the exact Cordis effect disposer. + */ +tools(provider: (context: AssembleContext) => ToolProviderResult): () => void + +/** + * Register a prompt variable in the calling context's scope. Scoped values + * shadow globals; invalid or duplicate names throw. A provider may return + * `undefined`, but rendering a section that references that value then fails. + * @param name - the `[a-z][a-z0-9_]*` reference name. + * @param provider - evaluated for each assembly. + * @returns the exact Cordis effect disposer. + */ +variable(name: string, provider: (context: AssembleContext) => string | undefined): () => void + +/** + * Assemble global and scoped providers, detach tool parameters, apply + * canonical ordering, then run the assembly waterfall. Scoped sections and + * variables shadow globals; the returned waterfall value is authoritative. + * @param context - the optional scope and plugin-defined assembly fields. + * @returns the authoritative post-waterfall assembly. + */ +async assemble(context: AssembleContext = {}): Promise +``` + +Source: [`packages/core/system-prompt/src/index.ts:314`](../../packages/core/system-prompt/src/index.ts) + + + +### `system-prompt/*` events + + + +#### `system-prompt/assemble` — waterfall + +Expert waterfall over the assembled sections, contexts, tools, and variables. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners receive only that scope's assemblies. The returned value is authoritative. A supplied signal controls only this explicit assembly request and must not be retained to control later turns. + +```ts cordis-catalog +/** + * Expert waterfall over the assembled sections, contexts, tools, and variables. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): scoped listeners + * receive only that scope's assemblies. The returned value is authoritative. + * A supplied signal controls only this explicit assembly request and must not + * be retained to control later turns. + * @param assembly - the mutable assembly built from registered providers. + * @param context - the caller's per-assembly context. + * @mode waterfall + */ +'system-prompt/assemble'(this: Scoped, assembly: PromptAssembly, context: AssembleContext, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/system-prompt/src/index.ts:29`](../../packages/core/system-prompt/src/index.ts) + + + +#### `system-prompt/change` — emit + +Emitted when any prompt provider changes. This registry notification is unfiltered because a global change affects every scope. + +```ts cordis-catalog +/** + * Emitted when any prompt provider changes. This registry notification is + * unfiltered because a global change affects every scope. + * @mode emit + */ +'system-prompt/change'(): void +``` + +Source: [`packages/core/system-prompt/src/index.ts:35`](../../packages/core/system-prompt/src/index.ts) + diff --git a/docs/subsystems/tasks.i18n.yaml b/docs/subsystems/tasks.i18n.yaml index 58920a53df..d28dcc2781 100644 --- a/docs/subsystems/tasks.i18n.yaml +++ b/docs/subsystems/tasks.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/tasks.md -tasks.md: a38055d3ef7aa18e62678f92eb5ac5ae2a09c205 -tasks.zh.md: f34d42e713c3a0c11cbf88d52e573bb100c52493 +# pnpm run verify-translation-pairing --write docs/subsystems/tasks.md +tasks.md: 6237607307cbab8511f968ed591e3e3b8b845c6a +tasks.zh.md: 99a6fcc4f54c1b52e5c6fd393327cc768f064297 diff --git a/docs/subsystems/tasks.md b/docs/subsystems/tasks.md index a38055d3ef..6237607307 100644 --- a/docs/subsystems/tasks.md +++ b/docs/subsystems/tasks.md @@ -152,3 +152,109 @@ interface TaskRead { ## Service behavior The abstract [`TaskService`](../../packages/tasks/tasks/src/index.ts) seam defines atomic `start`, caller-scoped `get` and `list`, `read`, `kill`, bounded `wait`, contained `onTaskDone` listeners, and the `attachSurface` availability fence; [`LocalTaskService`](../../packages/tasks/tasks-local/src/index.ts) is the process-local implementation. Authorization compares owner sessions; owner cleanup selects the exact registered `Agent` instance. See [`dsh-tasks`](../../packages/tasks/tasks/README.md) for the seam contract, [`dsh-tasks-local`](../../packages/tasks/tasks-local/README.md) for the registry lifecycle, and [`dsh-tool-tasks`](../../packages/tasks/tool-tasks/README.md) for the model-facing surface. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tasks` — `TaskService` (abstract seam) + +Abstract background task registry. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.tasks` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- Registrations outlive producer and control-surface fibers. Owner and service disposal cancel live work and await compliant producers; a throwing teardown cancel force-fails only the record. +- Owned-task access is fenced by the owner's session id. Ids are predictable, so authorization — not secrecy — is the boundary. +- Settlement is first-wins: one terminal record, one round of contained listener notification, and released waiters, even against a late producer outcome. +- start refuses work while no control surface is attached, so a producer cannot start work that callers cannot collect or stop. + +```ts cordis-catalog +/** + * Preflight access, validation, and owner cleanup before starting and + * atomically registering work. A throwing starter leaves nothing registered; + * after it returns, registration cannot fail. Settlement records the outcome, + * notifies listeners, and releases waiters. + * @param spec - task identity, owner, and synchronous starter. + * @returns the registry-issued `-N` id. + */ +abstract start(spec: TaskStart): TaskId + +/** + * List caller-owned and unowned tasks in registration order without exposing + * another session's labels. + * @param caller - reading agent; a non-agent caller sees only unowned tasks. + * @returns fresh snapshots. + */ +abstract list(caller?: Agent): TaskSnapshot[] + +/** + * Return a non-consuming snapshot without changing its read cursor or notice + * state. Throws for an unknown or foreign task. + * @param id - task to look up. + * @param caller - reading agent checked against the owner. + * @returns a fresh snapshot. + */ +abstract get(id: TaskId, caller?: Agent): TaskSnapshot + +/** + * Read the next stream delta, or the idempotent final output after settlement. + * A terminal read marks the task reported. Throws for an unknown or foreign + * task. + * @param id - task to read. + * @param caller - reading agent checked against the owner. + * @returns output text and the post-read snapshot. + */ +abstract read(id: TaskId, caller?: Agent): TaskRead + +/** + * Request cancellation, then mark the task stopping and reported. A producer + * throw propagates without changing task state. Throws for an unknown or + * foreign task. + * @param id - task to cancel. + * @param caller - killing agent checked against the owner. + * @param reason - logged reason forwarded to the producer. + * @returns `requested` for live work, otherwise `already-finished`. + */ +abstract kill(id: TaskId, caller?: Agent, reason?: string): 'requested' | 'already-finished' + +/** + * Wait for settlement or timeout without cancelling the task. Caller abort + * rejects only while the task is live; after settlement the terminal + * snapshot wins so a notice suppressed for this waiter is still delivered. + * Throws for invalid, unknown, or foreign input. + * @param id - task to wait for. + * @param timeoutMs - positive finite wait bound in milliseconds. + * @param caller - waiting agent checked against the owner. + * @param signal - optional cancellation of the wait itself. + * @returns snapshot at settlement or timeout. + */ +abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise + +/** + * Register an effect-scoped completion listener. Each listener is contained; + * returned promises are observed but not awaited. No listener runs after + * service disposal. + * @param listener - receives each terminal snapshot and its exact owner. + * @returns disposer that unregisters the listener. + */ +abstract onTaskDone(listener: TaskDoneListener): () => void + +/** + * Attach an effect-scoped surface that can read and stop tasks. {@link start} + * refuses work while none is attached. + * @param name - diagnostic label; duplicate names remain independent. + * @returns disposer that detaches this surface. + */ +abstract attachSurface(name: string): () => void +``` + +Types: [Agent](core.md) + +Source: [`packages/tasks/tasks/src/index.ts:50`](../../packages/tasks/tasks/src/index.ts) + diff --git a/docs/subsystems/tasks.zh.md b/docs/subsystems/tasks.zh.md index f34d42e713..99a6fcc4f5 100644 --- a/docs/subsystems/tasks.zh.md +++ b/docs/subsystems/tasks.zh.md @@ -152,3 +152,109 @@ interface TaskRead { ## 服务行为 抽象的 [`TaskService`](../../packages/tasks/tasks/src/index.ts) seam 定义原子 `start`、限定调用方作用域的 `get` 和 `list`、`read`、`kill`、有界 `wait`、故障隔离的 `onTaskDone` 监听器,以及 `attachSurface` 可用性防线;[`LocalTaskService`](../../packages/tasks/tasks-local/src/index.ts) 是其进程局部实现。授权会比较拥有者会话;拥有者清理会选择确切的已注册 `Agent` 实例。seam 契约见 [`dsh-tasks`](../../packages/tasks/tasks/README.md),注册表生命周期见 [`dsh-tasks-local`](../../packages/tasks/tasks-local/README.md),面向模型的接口见 [`dsh-tool-tasks`](../../packages/tasks/tool-tasks/README.md)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tasks` — `TaskService` (abstract seam) + +Abstract background task registry. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.tasks` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior). + +Implementations must honor these semantics: + +- Registrations outlive producer and control-surface fibers. Owner and service disposal cancel live work and await compliant producers; a throwing teardown cancel force-fails only the record. +- Owned-task access is fenced by the owner's session id. Ids are predictable, so authorization — not secrecy — is the boundary. +- Settlement is first-wins: one terminal record, one round of contained listener notification, and released waiters, even against a late producer outcome. +- start refuses work while no control surface is attached, so a producer cannot start work that callers cannot collect or stop. + +```ts cordis-catalog +/** + * Preflight access, validation, and owner cleanup before starting and + * atomically registering work. A throwing starter leaves nothing registered; + * after it returns, registration cannot fail. Settlement records the outcome, + * notifies listeners, and releases waiters. + * @param spec - task identity, owner, and synchronous starter. + * @returns the registry-issued `-N` id. + */ +abstract start(spec: TaskStart): TaskId + +/** + * List caller-owned and unowned tasks in registration order without exposing + * another session's labels. + * @param caller - reading agent; a non-agent caller sees only unowned tasks. + * @returns fresh snapshots. + */ +abstract list(caller?: Agent): TaskSnapshot[] + +/** + * Return a non-consuming snapshot without changing its read cursor or notice + * state. Throws for an unknown or foreign task. + * @param id - task to look up. + * @param caller - reading agent checked against the owner. + * @returns a fresh snapshot. + */ +abstract get(id: TaskId, caller?: Agent): TaskSnapshot + +/** + * Read the next stream delta, or the idempotent final output after settlement. + * A terminal read marks the task reported. Throws for an unknown or foreign + * task. + * @param id - task to read. + * @param caller - reading agent checked against the owner. + * @returns output text and the post-read snapshot. + */ +abstract read(id: TaskId, caller?: Agent): TaskRead + +/** + * Request cancellation, then mark the task stopping and reported. A producer + * throw propagates without changing task state. Throws for an unknown or + * foreign task. + * @param id - task to cancel. + * @param caller - killing agent checked against the owner. + * @param reason - logged reason forwarded to the producer. + * @returns `requested` for live work, otherwise `already-finished`. + */ +abstract kill(id: TaskId, caller?: Agent, reason?: string): 'requested' | 'already-finished' + +/** + * Wait for settlement or timeout without cancelling the task. Caller abort + * rejects only while the task is live; after settlement the terminal + * snapshot wins so a notice suppressed for this waiter is still delivered. + * Throws for invalid, unknown, or foreign input. + * @param id - task to wait for. + * @param timeoutMs - positive finite wait bound in milliseconds. + * @param caller - waiting agent checked against the owner. + * @param signal - optional cancellation of the wait itself. + * @returns snapshot at settlement or timeout. + */ +abstract wait(id: TaskId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise + +/** + * Register an effect-scoped completion listener. Each listener is contained; + * returned promises are observed but not awaited. No listener runs after + * service disposal. + * @param listener - receives each terminal snapshot and its exact owner. + * @returns disposer that unregisters the listener. + */ +abstract onTaskDone(listener: TaskDoneListener): () => void + +/** + * Attach an effect-scoped surface that can read and stop tasks. {@link start} + * refuses work while none is attached. + * @param name - diagnostic label; duplicate names remain independent. + * @returns disposer that detaches this surface. + */ +abstract attachSurface(name: string): () => void +``` + +Types: [Agent](core.md) + +Source: [`packages/tasks/tasks/src/index.ts:50`](../../packages/tasks/tasks/src/index.ts) + diff --git a/docs/subsystems/telemetry.i18n.yaml b/docs/subsystems/telemetry.i18n.yaml index 2eb634bb2d..19cd6f6851 100644 --- a/docs/subsystems/telemetry.i18n.yaml +++ b/docs/subsystems/telemetry.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md -telemetry.md: cf570723650c32f048ee797923a58f09c777455d -telemetry.zh.md: 1bcaa86264e3afd741d66d6ee718946e6151496f +telemetry.md: 131c5814f29d70c356989e62fda1db5c1c5448d6 +telemetry.zh.md: 5d4a9d9994457bc06a9e4194c4807f88789a3cf0 diff --git a/docs/subsystems/telemetry.md b/docs/subsystems/telemetry.md index cf57072365..131c5814f2 100644 --- a/docs/subsystems/telemetry.md +++ b/docs/subsystems/telemetry.md @@ -105,8 +105,76 @@ interface TelemetryBackend { } ``` -`Telemetry` (`ctx.telemetry`, [signatures](../cordis-catalog/services.md#ctxtelemetry--telemetry-abstract-seam)) is the contract's loadable form — one implementation per context, duplicate load throws — and a backend composes the seam's `TelemetryCoordinator` in its constructor to install the capture side. +`Telemetry` (`ctx.telemetry`, [signatures](#ctxtelemetry--telemetry-abstract-seam)) is the contract's loadable form — one implementation per context, duplicate load throws — and a backend composes the seam's `TelemetryCoordinator` in its constructor to install the capture side. ## The redact waterfall: `telemetry/record` -Every record passes the `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics) between projection and `emit()` ([event entry](../cordis-catalog/events.md#telemetryrecord--waterfall)). The seam ships NO rules of its own: with no listener mounted, records reach the backend exactly as captured, so exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath; a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only — the canonical session log is never rewritten. +Every record passes the `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics) between projection and `emit()` ([event entry](#telemetryrecord--waterfall)). The seam ships NO rules of its own: with no listener mounted, records reach the backend exactly as captured, so exported data is precisely as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath; a throwing listener withholds that one record fail-closed inside the coordinator's containment. Redaction applies to the exported copy only — the canonical session log is never rewritten. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.telemetry` — `Telemetry` (abstract seam) + +The backend contract in its loadable form: one implementation per context — the cordis `Service` registration under the `telemetry` key throws on a duplicate, cordis' standard behavior. A backend composes a TelemetryCoordinator in its constructor to install the capture side. + +```ts cordis-catalog +/** + * See {@link TelemetryBackend.emit} — the seam declaration is the contract's one home. + * @param record - the logical record to report; owned by the backend after the call. + */ +abstract emit(record: TelemetryRecord): void + +/** See {@link TelemetryBackend.flush}. */ +flush?(): void + +/** + * See {@link TelemetryBackend.shutdown}. + * @returns resolves when the backend's pipeline has quiesced. + */ +abstract shutdown(): Promise +``` + +Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts) + + + +### `telemetry/*` events + + + +#### `telemetry/record` — waterfall + +Transform one outbound record before it reaches the backend. This waterfall is the seam's redaction extension point. It ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Live capture dispatches at append time; on-demand capture dispatches while reading the canonical log. Redaction applies to the exported copy only; the canonical session log is never rewritten. + +```ts cordis-catalog +/** + * Transform one outbound record before it reaches the backend. This + * waterfall is the seam's redaction extension point. It ships NO rules + * of its own: the + * innermost `next()` passes the record through unchanged, and with no + * listener mounted records reach the backend as captured, so exported + * data is exactly as clean as the rules a deployment mounts. Listeners + * stack by transforming `next()`'s return value; returning without + * `next()` replaces everything beneath. Dispatched synchronously on the + * capture hot path inside the coordinator's containment: a throwing + * listener withholds that one record (fail-closed) and never reaches the + * agent loop. Live capture dispatches at append time; on-demand capture + * dispatches while reading the canonical log. Redaction applies to the + * exported copy only; the canonical session log is never rewritten. + * @param record - the candidate record, already the coordinator's own deep + * copy; listeners return a (possibly new) record and must not mutate it. + * @mode waterfall + */ +'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord +``` + +Source: [`packages/session/session-telemetry/src/index.ts:43`](../../packages/session/session-telemetry/src/index.ts) + diff --git a/docs/subsystems/telemetry.zh.md b/docs/subsystems/telemetry.zh.md index 1bcaa86264..5d4a9d9994 100644 --- a/docs/subsystems/telemetry.zh.md +++ b/docs/subsystems/telemetry.zh.md @@ -105,8 +105,76 @@ interface TelemetryBackend { } ``` -`Telemetry`(`ctx.telemetry`,[签名](../cordis-catalog/services.md#ctxtelemetry--telemetry-abstract-seam))是该契约的可加载形态:每个上下文只允许一个实现,重复加载会抛出异常;后端在其构造函数中组合 seam 的 `TelemetryCoordinator`,以此装配捕获侧。 +`Telemetry`(`ctx.telemetry`,[签名](#ctxtelemetry--telemetry-abstract-seam))是该契约的可加载形态:每个上下文只允许一个实现,重复加载会抛出异常;后端在其构造函数中组合 seam 的 `TelemetryCoordinator`,以此装配捕获侧。 ## 脱敏 waterfall:`telemetry/record` -每条记录在投影与 `emit()` 之间都要经过 `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics)([事件条目](../cordis-catalog/events.md#telemetryrecord--waterfall))。seam 自身不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。 +每条记录在投影与 `emit()` 之间都要经过 `telemetry/record` [waterfall](../cordis-primer.md#cordis-waterfall-semantics)([事件条目](#telemetryrecord--waterfall))。seam 自身不带任何规则:未挂载监听器时,记录以捕获时的原样到达后端;导出数据能干净到什么程度,恰恰取决于部署方挂载了什么规则。监听器通过变换 `next()` 的返回值来堆叠;不调用 `next()` 就返回,即替换其下方的全部逻辑;抛出异常的监听器会在协调器的隔离范围内以 fail-closed 方式扣下这一条记录。脱敏只作用于导出副本;权威会话日志永不改写。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.telemetry` — `Telemetry` (abstract seam) + +The backend contract in its loadable form: one implementation per context — the cordis `Service` registration under the `telemetry` key throws on a duplicate, cordis' standard behavior. A backend composes a TelemetryCoordinator in its constructor to install the capture side. + +```ts cordis-catalog +/** + * See {@link TelemetryBackend.emit} — the seam declaration is the contract's one home. + * @param record - the logical record to report; owned by the backend after the call. + */ +abstract emit(record: TelemetryRecord): void + +/** See {@link TelemetryBackend.flush}. */ +flush?(): void + +/** + * See {@link TelemetryBackend.shutdown}. + * @returns resolves when the backend's pipeline has quiesced. + */ +abstract shutdown(): Promise +``` + +Source: [`packages/session/session-telemetry/src/index.ts:140`](../../packages/session/session-telemetry/src/index.ts) + + + +### `telemetry/*` events + + + +#### `telemetry/record` — waterfall + +Transform one outbound record before it reaches the backend. This waterfall is the seam's redaction extension point. It ships NO rules of its own: the innermost `next()` passes the record through unchanged, and with no listener mounted records reach the backend as captured, so exported data is exactly as clean as the rules a deployment mounts. Listeners stack by transforming `next()`'s return value; returning without `next()` replaces everything beneath. Dispatched synchronously on the capture hot path inside the coordinator's containment: a throwing listener withholds that one record (fail-closed) and never reaches the agent loop. Live capture dispatches at append time; on-demand capture dispatches while reading the canonical log. Redaction applies to the exported copy only; the canonical session log is never rewritten. + +```ts cordis-catalog +/** + * Transform one outbound record before it reaches the backend. This + * waterfall is the seam's redaction extension point. It ships NO rules + * of its own: the + * innermost `next()` passes the record through unchanged, and with no + * listener mounted records reach the backend as captured, so exported + * data is exactly as clean as the rules a deployment mounts. Listeners + * stack by transforming `next()`'s return value; returning without + * `next()` replaces everything beneath. Dispatched synchronously on the + * capture hot path inside the coordinator's containment: a throwing + * listener withholds that one record (fail-closed) and never reaches the + * agent loop. Live capture dispatches at append time; on-demand capture + * dispatches while reading the canonical log. Redaction applies to the + * exported copy only; the canonical session log is never rewritten. + * @param record - the candidate record, already the coordinator's own deep + * copy; listeners return a (possibly new) record and must not mutate it. + * @mode waterfall + */ +'telemetry/record'(record: TelemetryRecord, next: () => TelemetryRecord): TelemetryRecord +``` + +Source: [`packages/session/session-telemetry/src/index.ts:43`](../../packages/session/session-telemetry/src/index.ts) + diff --git a/docs/subsystems/token-meter.i18n.yaml b/docs/subsystems/token-meter.i18n.yaml index 3f58d10153..e348aca7a0 100644 --- a/docs/subsystems/token-meter.i18n.yaml +++ b/docs/subsystems/token-meter.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/token-meter.md -token-meter.md: 05784e294485a11acf0e4c8972e4083b1786c943 -token-meter.zh.md: 0474d86188a111014e6d72e9962121c08a228d73 +# pnpm run verify-translation-pairing --write docs/subsystems/token-meter.md +token-meter.md: 0f53955b3e6afce84520314f4de453edaa897158 +token-meter.zh.md: 3ba22c54d599982d3f7bdbcb9160b6ffc3255e07 diff --git a/docs/subsystems/token-meter.md b/docs/subsystems/token-meter.md index 05784e2944..0f53955b3e 100644 --- a/docs/subsystems/token-meter.md +++ b/docs/subsystems/token-meter.md @@ -41,3 +41,50 @@ interface TokenSurfaceNode { ``` Surface order is authoritative; replacement nodes can have higher durable seqs than later positional nodes. The snapshot is immutable and does not grow when the underlying replay fold advances. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tokenMeter` — `TokenMeterService` + +Replay owner for one service-wide estimator and isolated per-session folds. + +```ts cordis-catalog +/** + * Measure current request pressure and surface through the durable tail. + * + * Provider usage is reused only when the latest successful call's canonical + * request envelope matches `requestHeader` and its total is no lower than + * that call's full heuristic anchor; otherwise the complete envelope and + * surface are heuristically repriced. + * + * `requestHeader` affects request pressure only; surface fields always + * describe the current session surface. Every call clones those positional + * nodes, so measurement is O(surface). + * + * @param session - session to replay through its current durable tail. + * @param requestHeader - optional effective request envelope replacing the latest logged header. + * @returns a detached deeply immutable pressure and surface measurement. + */ +measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement + +/** + * Heuristically price one model-visible message (instance face of the pure + * `estimateMessage` export from `estimate.ts`). + * @param message - message to price without mutation. + * @returns content and role-framing tokens under the fixed service heuristic. + */ +estimateMessage(message: Message): number +``` + +Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) + +Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) + diff --git a/docs/subsystems/token-meter.zh.md b/docs/subsystems/token-meter.zh.md index 0474d86188..3ba22c54d5 100644 --- a/docs/subsystems/token-meter.zh.md +++ b/docs/subsystems/token-meter.zh.md @@ -41,3 +41,50 @@ interface TokenSurfaceNode { ``` 表层顺序具有权威性;替换节点的持久 seq 可能高于位置排在其后的节点。该快照不可变,不会随底层回放折叠推进而增长。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tokenMeter` — `TokenMeterService` + +Replay owner for one service-wide estimator and isolated per-session folds. + +```ts cordis-catalog +/** + * Measure current request pressure and surface through the durable tail. + * + * Provider usage is reused only when the latest successful call's canonical + * request envelope matches `requestHeader` and its total is no lower than + * that call's full heuristic anchor; otherwise the complete envelope and + * surface are heuristically repriced. + * + * `requestHeader` affects request pressure only; surface fields always + * describe the current session surface. Every call clones those positional + * nodes, so measurement is O(surface). + * + * @param session - session to replay through its current durable tail. + * @param requestHeader - optional effective request envelope replacing the latest logged header. + * @returns a detached deeply immutable pressure and surface measurement. + */ +measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement + +/** + * Heuristically price one model-visible message (instance face of the pure + * `estimateMessage` export from `estimate.ts`). + * @param message - message to price without mutation. + * @returns content and role-framing tokens under the fixed service heuristic. + */ +estimateMessage(message: Message): number +``` + +Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) + +Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) + diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index a30f122038..8b0aa47c9a 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/tools.md -tools.md: 853eed16cff451edcc32bc3aa5c6bc7cabb0f518 -tools.zh.md: ec8809f6bebd185404828c5c5879f8eff832ea9a +tools.md: 5d72bd54f0dc5995218087030f24c467c8ccbb38 +tools.zh.md: 2340d4c2d92c51aa9cc89f4694b626c76d05140b diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index 853eed16cf..5d72bd54f0 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -455,3 +455,241 @@ How a tool wants its call shown in a UI (an editor tool-call card, a CLI log lin `ToolCallKind` (`'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other'`) picks an icon on a generic card. `FileLocation` (`{ path, line? }`), `FileDiff` (`{ path, oldText, newText }`), and `ReadFileLine` (`{ number, text }`, one 1-based numbered line of a read window) are the shared file-card vocabulary. The design is pinned in [the render-intent-union Agent Note](../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md); host/client runtimes project this neutral vocabulary into their own views. The full presentation field docs live in [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts). The `bash` schema and executor are on [bash.md](bash.md); generic background controls are on [tasks.md](tasks.md). + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tools` — `ToolRegistry` + +Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch. + +```ts cordis-catalog +/** + * Register globally or in the calling agent scope. Scoped tools shadow + * globals; duplicates within one layer and the reserved `run_code` name fail. + * @param definition - tool schema, execution, and optional finalization/presentation callbacks. + * @returns the exact disposer that unregisters the tool. + */ +register(definition: ToolDefinition): () => void + +/** + * Restrict global tools for the calling agent scope. Empty filters, unknown + * names, scope-local names, and reserved transport names fail. Restrictions + * intersect; scoped registrations remain visible. + * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove). + * @returns the exact disposer that lifts this restriction. + */ +restrict(filter: ToolRestriction): () => void + +/** + * Register a monotonic guard after the extensible `tools/pre-execute` + * waterfall. A plain-context guard applies globally; one registered through + * `agent.ctx` applies only to that agent. Any matching guard may deny by + * returning a reason, while no guard can force-allow a call another guard + * denied. The exact effect disposer is returned for ordered ownership and + * HMR cleanup. + * @param guard - synchronous check; a returned string denies the execution. + * @returns the exact disposer that unregisters the guard. + */ +guard(guard: ToolGuard): () => void + +/** + * Look up a tool as one scope sees it (scoped + * shadows global; a restricted-away global reads as absent). Presenters pass + * the calling agent so the rendered card matches the definition that + * actually executed. + * @param name - the tool name as registered. + * @param scope - the viewing scope (the agent); omitted = the global view. + * @returns the definition the scope resolves, or undefined when none is visible. + */ +get(name: string, scope?: ScopeKey): ToolDefinition | undefined + +/** + * Project visible definitions onto the allowlisted model-facing schema fields, + * excluding execution and presentation callbacks. + * @param scope - the viewing scope (the agent); omitted = the global view. + * @returns one deep-cloned schema per visible tool. + */ +schemas(scope?: ScopeKey): ToolSchema[] + +/** + * Classify a pending call through the caller's visible tool definition. Only + * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or + * throwing classifiers are exclusive. + * @param exec - call name, parsed arguments, and optional agent scope. + * @returns the fail-closed scheduling mode. + */ +executionMode(exec: ToolExecutionInput): ToolExecutionMode + +/** + * Execute through pre-policy, guards, around-dispatch, post-policy, + * definition-owned content finalization, and final notification. Tool and + * listener failures resolve as materialized error results; an invisible tool + * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen + * snapshot final observers receive. Cancellation + * arriving after entry and before final result materialization skips a + * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a + * successful started outcome with `ABORTED`; already-started work is still + * drained and may retain a tool-owned structured error. + * @param exec - the typed same-process call input. The registry assigns its + * correlation token before policy begins. + * @returns the materialized final result. + */ +async execute(exec: ToolExecutionInput): Promise +``` + +Types: [ScopeKey](scope.md) + +Source: [`packages/core/tools/src/index.ts:739`](../../packages/core/tools/src/index.ts) + + + +### `tools/*` events + + + +#### `tools/change` — emit + +A tool was registered or unregistered, or a scoped restriction changed (the available tool set changed — possibly for one scope only). An UNFILTERED registry-subject notification, deliberately not scope-filtered dispatch: a global change concerns every agent's next assembly, so a scoped listener subscribing here sees every change, not just its own scope's. + +```ts cordis-catalog +/** + * A tool was registered or unregistered, or a scoped restriction changed + * (the available tool set changed — possibly for one scope only). An + * UNFILTERED registry-subject notification, deliberately not scope-filtered + * dispatch: a global change concerns every agent's next assembly, so a + * scoped listener subscribing here sees every change, not just its own + * scope's. + * @mode emit + */ +'tools/change'(): void +``` + +Source: [`packages/core/tools/src/index.ts:191`](../../packages/core/tools/src/index.ts) + + + +#### `tools/code-dispatch-log` — waterfall + +Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bridge appends its `tool/code-dispatch` event. `next()` keeps the content unchanged; a listener may return replacement blocks (e.g. the spill policy's preview + locator for an oversized text result). Only the logged copy is affected — the program already received the complete value, and the model sees neither. A throwing listener is contained: the bridge falls back to logging the unshaped content. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. + +```ts cordis-catalog +/** + * Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before + * the bridge appends its `tool/code-dispatch` event. `next()` keeps the + * content unchanged; a listener may return replacement blocks (e.g. the + * spill policy's preview + locator for an oversized text result). Only the + * logged copy is affected — the program already received the complete + * value, and the model sees neither. A throwing listener is contained: + * the bridge falls back to logging the unshaped content. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. + * @param dispatch - the parent execution, sub-call identity, and the settled content to log. + * @mode waterfall + */ +'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise +``` + +Types: [ContentBlock](core.md) · [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) + + + +#### `tools/execute` — waterfall + +Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. The registry re-fuses the original caller signal before the body, so replacement cannot detach caller cancellation; wrappers must still restore their signal and reach quiescence. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns + * a normalized result; wrappers may change only `exec.signal`, while call + * identity remains immutable. The registry re-fuses the original caller + * signal before the body, so replacement cannot detach caller cancellation; + * wrappers must still restore their signal and reach quiescence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal). + * @mode waterfall + */ +'tools/execute'(this: Scoped, exec: ToolDispatchExecution, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:148`](../../packages/core/tools/src/index.ts) + + + +#### `tools/post-execute` — waterfall + +Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with the code selected by whether the tool body was invoked. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Accept, replace, enrich, or block a normalized dispatch result. `next()` + * accepts it unchanged; thrown tools still reach this seam as errors. Async + * listeners must observe `exec.signal`; after they settle, caller + * cancellation replaces only a successful accepted outcome with the code + * selected by whether the tool body was invoked. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the call that just ran (name, parsed arguments, caller agent). + * @param result - the dispatch outcome a listener may accept, replace, or block. + * @mode waterfall + */ +'tools/post-execute'(this: Scoped, exec: ToolExecution, result: Readonly, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:160`](../../packages/core/tools/src/index.ts) + + + +#### `tools/pre-execute` — waterfall + +Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Async gates must observe `exec.signal`; the registry rechecks cancellation after they settle but never abandons their promise. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Allow, deny, or ask before dispatch. `next()` delegates to allow; missing + * approval support turns `ask` into denial. Async gates must observe + * `exec.signal`; the registry rechecks cancellation after they settle but + * never abandons their promise. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the pending call (name, parsed arguments, caller agent). + * @mode waterfall + */ +'tools/pre-execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:137`](../../packages/core/tools/src/index.ts) + + + +#### `tools/result` — emit + +Observe the frozen, lossless-JSON final outcome. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. + +```ts cordis-catalog +/** + * Observe the frozen, lossless-JSON final outcome. Listener failures are contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. + * @param exec - the execution object that traversed the pipeline. + * @param result - a deep-frozen snapshot of the final returned result. + * @mode emit + */ +'tools/result'(this: Scoped, exec: Readonly, result: Readonly): undefined +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:181`](../../packages/core/tools/src/index.ts) + diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index ec8809f6be..2340d4c2d9 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -455,3 +455,241 @@ type ObjectJsonSchema = JsonSchemaNode & { type: 'object' } `ToolCallKind`(`'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other'`)用于为通用卡片选择图标。`FileLocation`(`{ path, line? }`)、`FileDiff`(`{ path, oldText, newText }`)与 `ReadFileLine`(`{ number, text }`,读取窗口中一行带 1-based 行号的内容)是共享的文件卡片词汇。该设计由[渲染意图联合类型 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md)固定;host/client 运行时将这套中性词汇投影为各自的视图。 完整的展示字段文档见 [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts)。`bash` schema 与执行器见 [bash.md](bash.md);通用后台控制见 [tasks.md](tasks.md)。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.tools` — `ToolRegistry` + +Tool registry and execution pipeline. Scoped registrations shadow globals; one visibility resolver feeds presentation, lookup, and dispatch. + +```ts cordis-catalog +/** + * Register globally or in the calling agent scope. Scoped tools shadow + * globals; duplicates within one layer and the reserved `run_code` name fail. + * @param definition - tool schema, execution, and optional finalization/presentation callbacks. + * @returns the exact disposer that unregisters the tool. + */ +register(definition: ToolDefinition): () => void + +/** + * Restrict global tools for the calling agent scope. Empty filters, unknown + * names, scope-local names, and reserved transport names fail. Restrictions + * intersect; scoped registrations remain visible. + * @param filter - global-surface mask: `allow` (keep only) and/or `deny` (remove). + * @returns the exact disposer that lifts this restriction. + */ +restrict(filter: ToolRestriction): () => void + +/** + * Register a monotonic guard after the extensible `tools/pre-execute` + * waterfall. A plain-context guard applies globally; one registered through + * `agent.ctx` applies only to that agent. Any matching guard may deny by + * returning a reason, while no guard can force-allow a call another guard + * denied. The exact effect disposer is returned for ordered ownership and + * HMR cleanup. + * @param guard - synchronous check; a returned string denies the execution. + * @returns the exact disposer that unregisters the guard. + */ +guard(guard: ToolGuard): () => void + +/** + * Look up a tool as one scope sees it (scoped + * shadows global; a restricted-away global reads as absent). Presenters pass + * the calling agent so the rendered card matches the definition that + * actually executed. + * @param name - the tool name as registered. + * @param scope - the viewing scope (the agent); omitted = the global view. + * @returns the definition the scope resolves, or undefined when none is visible. + */ +get(name: string, scope?: ScopeKey): ToolDefinition | undefined + +/** + * Project visible definitions onto the allowlisted model-facing schema fields, + * excluding execution and presentation callbacks. + * @param scope - the viewing scope (the agent); omitted = the global view. + * @returns one deep-cloned schema per visible tool. + */ +schemas(scope?: ScopeKey): ToolSchema[] + +/** + * Classify a pending call through the caller's visible tool definition. Only + * an exact `true` is parallel; unknown, hidden, undeclared, invalid, or + * throwing classifiers are exclusive. + * @param exec - call name, parsed arguments, and optional agent scope. + * @returns the fail-closed scheduling mode. + */ +executionMode(exec: ToolExecutionInput): ToolExecutionMode + +/** + * Execute through pre-policy, guards, around-dispatch, post-policy, + * definition-owned content finalization, and final notification. Tool and + * listener failures resolve as materialized error results; an invisible tool + * reports `UNKNOWN_TOOL`. The returned outcome is the same lossless, frozen + * snapshot final observers receive. Cancellation + * arriving after entry and before final result materialization skips a + * not-yet-started body with `ABORTED_BEFORE_DISPATCH` or replaces a + * successful started outcome with `ABORTED`; already-started work is still + * drained and may retain a tool-owned structured error. + * @param exec - the typed same-process call input. The registry assigns its + * correlation token before policy begins. + * @returns the materialized final result. + */ +async execute(exec: ToolExecutionInput): Promise +``` + +Types: [ScopeKey](scope.md) + +Source: [`packages/core/tools/src/index.ts:739`](../../packages/core/tools/src/index.ts) + + + +### `tools/*` events + + + +#### `tools/change` — emit + +A tool was registered or unregistered, or a scoped restriction changed (the available tool set changed — possibly for one scope only). An UNFILTERED registry-subject notification, deliberately not scope-filtered dispatch: a global change concerns every agent's next assembly, so a scoped listener subscribing here sees every change, not just its own scope's. + +```ts cordis-catalog +/** + * A tool was registered or unregistered, or a scoped restriction changed + * (the available tool set changed — possibly for one scope only). An + * UNFILTERED registry-subject notification, deliberately not scope-filtered + * dispatch: a global change concerns every agent's next assembly, so a + * scoped listener subscribing here sees every change, not just its own + * scope's. + * @mode emit + */ +'tools/change'(): void +``` + +Source: [`packages/core/tools/src/index.ts:191`](../../packages/core/tools/src/index.ts) + + + +#### `tools/code-dispatch-log` — waterfall + +Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bridge appends its `tool/code-dispatch` event. `next()` keeps the content unchanged; a listener may return replacement blocks (e.g. the spill policy's preview + locator for an oversized text result). Only the logged copy is affected — the program already received the complete value, and the model sees neither. A throwing listener is contained: the bridge falls back to logging the unshaped content. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. + +```ts cordis-catalog +/** + * Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before + * the bridge appends its `tool/code-dispatch` event. `next()` keeps the + * content unchanged; a listener may return replacement blocks (e.g. the + * spill policy's preview + locator for an oversized text result). Only the + * logged copy is affected — the program already received the complete + * value, and the model sees neither. A throwing listener is contained: + * the bridge falls back to logging the unshaped content. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's dispatches. + * @param dispatch - the parent execution, sub-call identity, and the settled content to log. + * @mode waterfall + */ +'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise +``` + +Types: [ContentBlock](core.md) · [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) + + + +#### `tools/execute` — waterfall + +Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns a normalized result; wrappers may change only `exec.signal`, while call identity remains immutable. The registry re-fuses the original caller signal before the body, so replacement cannot detach caller cancellation; wrappers must still restore their signal and reach quiescence. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Around-dispatch waterfall for timeout, retry, or metrics. `next()` returns + * a normalized result; wrappers may change only `exec.signal`, while call + * identity remains immutable. The registry re-fuses the original caller + * signal before the body, so replacement cannot detach caller cancellation; + * wrappers must still restore their signal and reach quiescence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the allowed call about to dispatch (name, parsed arguments, caller agent, signal). + * @mode waterfall + */ +'tools/execute'(this: Scoped, exec: ToolDispatchExecution, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:148`](../../packages/core/tools/src/index.ts) + + + +#### `tools/post-execute` — waterfall + +Accept, replace, enrich, or block a normalized dispatch result. `next()` accepts it unchanged; thrown tools still reach this seam as errors. Async listeners must observe `exec.signal`; after they settle, caller cancellation replaces only a successful accepted outcome with the code selected by whether the tool body was invoked. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Accept, replace, enrich, or block a normalized dispatch result. `next()` + * accepts it unchanged; thrown tools still reach this seam as errors. Async + * listeners must observe `exec.signal`; after they settle, caller + * cancellation replaces only a successful accepted outcome with the code + * selected by whether the tool body was invoked. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the call that just ran (name, parsed arguments, caller agent). + * @param result - the dispatch outcome a listener may accept, replace, or block. + * @mode waterfall + */ +'tools/post-execute'(this: Scoped, exec: ToolExecution, result: Readonly, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:160`](../../packages/core/tools/src/index.ts) + + + +#### `tools/pre-execute` — waterfall + +Allow, deny, or ask before dispatch. `next()` delegates to allow; missing approval support turns `ask` into denial. Async gates must observe `exec.signal`; the registry rechecks cancellation after they settle but never abandons their promise. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + +```ts cordis-catalog +/** + * Allow, deny, or ask before dispatch. `next()` delegates to allow; missing + * approval support turns `ask` into denial. Async gates must observe + * `exec.signal`; the registry rechecks cancellation after they settle but + * never abandons their promise. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent's calls. + * @param exec - the pending call (name, parsed arguments, caller agent). + * @mode waterfall + */ +'tools/pre-execute'(this: Scoped, exec: ToolExecution, next: () => Promise): Promise +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:137`](../../packages/core/tools/src/index.ts) + + + +#### `tools/result` — emit + +Observe the frozen, lossless-JSON final outcome. Listener failures are contained. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. + +```ts cordis-catalog +/** + * Observe the frozen, lossless-JSON final outcome. Listener failures are contained. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): keyed by `exec.agent`. + * @param exec - the execution object that traversed the pipeline. + * @param result - a deep-frozen snapshot of the final returned result. + * @mode emit + */ +'tools/result'(this: Scoped, exec: Readonly, result: Readonly): undefined +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/tools/src/index.ts:181`](../../packages/core/tools/src/index.ts) + diff --git a/docs/subsystems/typert.i18n.yaml b/docs/subsystems/typert.i18n.yaml index 75b7837687..b8fdb092f2 100644 --- a/docs/subsystems/typert.i18n.yaml +++ b/docs/subsystems/typert.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/typert.md -typert.md: c70e50e2fea8455eb75dfdf8c309f659ab9cb2f9 -typert.zh.md: 2cd1636d4cc8dbcfa009073b4a8e1dcc8d5897e4 +# pnpm run verify-translation-pairing --write docs/subsystems/typert.md +typert.md: 8ce52837ceee8e9c8c4a61de28f972912faa2cf0 +typert.zh.md: ff6b45a517c75392ee87fa348d72bfae6cdf1efb diff --git a/docs/subsystems/typert.md b/docs/subsystems/typert.md index c70e50e2fe..8ce52837ce 100644 --- a/docs/subsystems/typert.md +++ b/docs/subsystems/typert.md @@ -201,3 +201,96 @@ interface TypeRTClientRemote extends TypeRTRemoteNamespaceMap { $mount(contribution: TypeRTRemoteContribution): Promise } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.typert` — `TypertRegistry` + +Registry of generated schemas, package reflection, invocations, and Remote dependency providers. + +```ts cordis-catalog +/** + * Register one generated contribution atomically for the calling fiber. + * Duplicate package-face identities, schemas, invocation ids, or endpoints + * reject the whole batch. + * @param contribution - generated schemas, reflection, and Host invocations. + * @returns the exact effect disposer that removes this contribution. + */ +register(contribution: TypertContribution): TypeRTDisposer + +/** + * Look up one schema by `#`. + * @param key - global schema key. + * @returns the live schema record, or `undefined` when absent. + */ +get(key: string): TypertSchemaRecord | undefined + +/** + * Resolve one required schema. + * @param key - global schema key. + * @returns the live schema record. + * @throws when the key is malformed, the package face is absent, or the schema is not contributed. + */ +resolve(key: string): TypertSchemaRecord + +/** + * Enumerate live schemas in registration order. + * @param filter - optional package and face restriction. + * @returns matching schema records. + */ +list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] + +/** + * Look up generated reflection for one package face. + * @param packageName - exact npm package name. + * @param face - face to query; defaults to the host runtime. + * @returns the live package record, or `undefined` when absent. + */ +getPackage(packageName: string, face: TypertFace = 'host'): TypertPackageRecord | undefined + +/** + * Enumerate generated package reflection in registration order. + * @param filter - optional package and face restriction. + * @returns matching package records. + */ +listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] + +/** + * Project a live Zod schema to JSON Schema without caching the result. + * @param key - global schema key. + * @param params - Zod projection parameters. + * @returns a fresh JSON Schema document. + */ +toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema +``` + +Types: [TypertContribution](invariants.md) · [TypertFace](invariants.md) · [TypertPackageFilter](invariants.md) · [TypertPackageRecord](invariants.md) · [TypertSchemaFilter](invariants.md) · [TypertSchemaRecord](invariants.md) + +Source: [`packages/typert/registry/src/service.ts:446`](../../packages/typert/registry/src/service.ts) + + + +### `ctx.typertGateway` — `TypertGatewayService` + +Resolve strict generated definitions or conservative SRC markers against current Cordis Services and TypeRT providers. + +```ts cordis-catalog +/** + * Invoke one live Remote method through strict generated reflection or SRC markers. + * @param request - decoded endpoint and exact named wire arguments. + * @returns the validated business result. + * @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity. + */ +async invoke(request: InvokeRemoteRequest): Promise +``` + +Source: [`packages/api/gateway/src/index.ts:78`](../../packages/api/gateway/src/index.ts) + diff --git a/docs/subsystems/typert.zh.md b/docs/subsystems/typert.zh.md index 2cd1636d4c..ff6b45a517 100644 --- a/docs/subsystems/typert.zh.md +++ b/docs/subsystems/typert.zh.md @@ -201,3 +201,96 @@ interface TypeRTClientRemote extends TypeRTRemoteNamespaceMap { $mount(contribution: TypeRTRemoteContribution): Promise } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.typert` — `TypertRegistry` + +Registry of generated schemas, package reflection, invocations, and Remote dependency providers. + +```ts cordis-catalog +/** + * Register one generated contribution atomically for the calling fiber. + * Duplicate package-face identities, schemas, invocation ids, or endpoints + * reject the whole batch. + * @param contribution - generated schemas, reflection, and Host invocations. + * @returns the exact effect disposer that removes this contribution. + */ +register(contribution: TypertContribution): TypeRTDisposer + +/** + * Look up one schema by `#`. + * @param key - global schema key. + * @returns the live schema record, or `undefined` when absent. + */ +get(key: string): TypertSchemaRecord | undefined + +/** + * Resolve one required schema. + * @param key - global schema key. + * @returns the live schema record. + * @throws when the key is malformed, the package face is absent, or the schema is not contributed. + */ +resolve(key: string): TypertSchemaRecord + +/** + * Enumerate live schemas in registration order. + * @param filter - optional package and face restriction. + * @returns matching schema records. + */ +list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] + +/** + * Look up generated reflection for one package face. + * @param packageName - exact npm package name. + * @param face - face to query; defaults to the host runtime. + * @returns the live package record, or `undefined` when absent. + */ +getPackage(packageName: string, face: TypertFace = 'host'): TypertPackageRecord | undefined + +/** + * Enumerate generated package reflection in registration order. + * @param filter - optional package and face restriction. + * @returns matching package records. + */ +listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] + +/** + * Project a live Zod schema to JSON Schema without caching the result. + * @param key - global schema key. + * @param params - Zod projection parameters. + * @returns a fresh JSON Schema document. + */ +toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema +``` + +Types: [TypertContribution](invariants.md) · [TypertFace](invariants.md) · [TypertPackageFilter](invariants.md) · [TypertPackageRecord](invariants.md) · [TypertSchemaFilter](invariants.md) · [TypertSchemaRecord](invariants.md) + +Source: [`packages/typert/registry/src/service.ts:446`](../../packages/typert/registry/src/service.ts) + + + +### `ctx.typertGateway` — `TypertGatewayService` + +Resolve strict generated definitions or conservative SRC markers against current Cordis Services and TypeRT providers. + +```ts cordis-catalog +/** + * Invoke one live Remote method through strict generated reflection or SRC markers. + * @param request - decoded endpoint and exact named wire arguments. + * @returns the validated business result. + * @throws {@link TypertGatewayError} for dispatch, provider, or boundary failures; lookup-policy and business errors retain identity. + */ +async invoke(request: InvokeRemoteRequest): Promise +``` + +Source: [`packages/api/gateway/src/index.ts:78`](../../packages/api/gateway/src/index.ts) + diff --git a/docs/subsystems/user-interaction.i18n.yaml b/docs/subsystems/user-interaction.i18n.yaml index 80ebb810c8..342cf9b9f7 100644 --- a/docs/subsystems/user-interaction.i18n.yaml +++ b/docs/subsystems/user-interaction.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/user-interaction.md -user-interaction.md: ec22eb28e9554d6454bf2670f73f2e9b199df014 -user-interaction.zh.md: 814f8911e41aecf568f627f76f8239bac4ea719b +user-interaction.md: a19155ae06af0133ae468c004b2e3b66f1de3fb8 +user-interaction.zh.md: dc5845a976068981b2761e26502eaf7dd95dcb01 diff --git a/docs/subsystems/user-interaction.md b/docs/subsystems/user-interaction.md index ec22eb28e9..a19155ae06 100644 --- a/docs/subsystems/user-interaction.md +++ b/docs/subsystems/user-interaction.md @@ -132,3 +132,47 @@ class UserInteractionError extends HarnessError { } } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.userInteraction` — `UserInteractionService` + +`ctx.userInteraction`: one active UI provider plus an `ask()` surface. + +```ts cordis-catalog +/** + * Register the UI provider. Only one provider may be active in a context. + * + * @param provider UI-side implementation that collects answers. + * @returns Disposer that unregisters this provider. + */ +registerProvider(provider: UserInteractionProvider): () => void + +/** + * Ask the active UI provider and wait for the user's answer. + * + * When a caller supplies an agent, human interaction is valid only for the + * exact live runtime root. Runtime ownership, not durable session lineage, + * decides this boundary: an owned child has no human answerer and would + * block forever, while a lineage-bearing session resumed as a new runtime + * root may ask normally. + * + * @param request Questions, owner agent, and abort signal. + * @returns The answer chosen or typed by the human. + * @throws {UserInteractionError} code `CALLER_NOT_LIVE` when a supplied + * agent is not the registry's exact live instance, or `DELEGATED_CALLER` + * when that live agent is owned by another agent. + */ +async ask(request: AskUserQuestionRequest): Promise +``` + +Source: [`packages/interaction/user-interaction/src/index.ts:51`](../../packages/interaction/user-interaction/src/index.ts) + diff --git a/docs/subsystems/user-interaction.zh.md b/docs/subsystems/user-interaction.zh.md index 814f8911e4..dc5845a976 100644 --- a/docs/subsystems/user-interaction.zh.md +++ b/docs/subsystems/user-interaction.zh.md @@ -132,3 +132,47 @@ class UserInteractionError extends HarnessError { } } ``` + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.userInteraction` — `UserInteractionService` + +`ctx.userInteraction`: one active UI provider plus an `ask()` surface. + +```ts cordis-catalog +/** + * Register the UI provider. Only one provider may be active in a context. + * + * @param provider UI-side implementation that collects answers. + * @returns Disposer that unregisters this provider. + */ +registerProvider(provider: UserInteractionProvider): () => void + +/** + * Ask the active UI provider and wait for the user's answer. + * + * When a caller supplies an agent, human interaction is valid only for the + * exact live runtime root. Runtime ownership, not durable session lineage, + * decides this boundary: an owned child has no human answerer and would + * block forever, while a lineage-bearing session resumed as a new runtime + * root may ask normally. + * + * @param request Questions, owner agent, and abort signal. + * @returns The answer chosen or typed by the human. + * @throws {UserInteractionError} code `CALLER_NOT_LIVE` when a supplied + * agent is not the registry's exact live instance, or `DELEGATED_CALLER` + * when that live agent is owned by another agent. + */ +async ask(request: AskUserQuestionRequest): Promise +``` + +Source: [`packages/interaction/user-interaction/src/index.ts:51`](../../packages/interaction/user-interaction/src/index.ts) + diff --git a/docs/subsystems/web.i18n.yaml b/docs/subsystems/web.i18n.yaml index e8ae658d3a..f3f0ab9758 100644 --- a/docs/subsystems/web.i18n.yaml +++ b/docs/subsystems/web.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/web.md -web.md: 20d07240c9d9fea2f1f5abbac810f349a3e81f9b -web.zh.md: e2982ba571353752e3a7d10130599e8c8fe941f8 +# pnpm run verify-translation-pairing --write docs/subsystems/web.md +web.md: f300bf2daef172fe4a69c00f36c804cf874c189d +web.zh.md: 0c72e343c1b6ea5de8e7c296014da35b0291a33b diff --git a/docs/subsystems/web.md b/docs/subsystems/web.md index 20d07240c9..f300bf2dae 100644 --- a/docs/subsystems/web.md +++ b/docs/subsystems/web.md @@ -133,3 +133,70 @@ Selection never depends on registration, config, or HMR order: a capability has ## The service `WebService` registers search and fetch providers, rejects duplicate ids with `WEB_DUPLICATE_PROVIDER`, and resolves providers at execution time with structured selection errors. The local fetch backend accepts only HTTP(S), rejects credentials, caps redirects, bytes, characters, and time, revalidates every same-origin redirect hop, and decodes the body; the tool owns presentation. Private-network blocking is deferred, so do not enable `web_fetch` where it can reach sensitive internal targets. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.web` — `WebService` + +The web access service. Registered as `ctx.web` (one instance per context). + +Selection semantics (resolved at execution time, never order-dependent): + +- A configured id that is registered and `available()` → that provider. +- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. +- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. +- No id configured, exactly one registered usable provider → that provider. +- No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. +- No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. + +```ts cordis-catalog +/** + * Register a search provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` + * if its id is already registered for search. Returns a disposer; disposed + * with the calling fiber. + * @param provider - the provider; its `id` is the registry key. + * @returns the disposer that unregisters the provider. + */ +registerSearchProvider(provider: WebSearchProvider): () => void + +/** + * Register a fetch provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` + * if its id is already registered for fetch. Returns a disposer; disposed + * with the calling fiber. + * @param provider - the provider; its `id` is the registry key. + * @returns the disposer that unregisters the provider. + */ +registerFetchProvider(provider: WebFetchProvider): () => void + +/** + * Run one search through the selected provider. Resolves the provider at call + * time with the selection rules above; throws {@link WebError} when the + * capability cannot run. The seam enforces `request.maxResults` on the result: + * if the provider over-returns, `sources[]` is truncated and `truncated` set. + * @param request - the query plus result-shaping options. + * @param signal - optional cancellation signal forwarded to the provider. + * @returns the provider's results, capped to `request.maxResults`. + */ +async search(request: WebSearchRequest, signal?: AbortSignal): Promise + +/** + * Retrieve one URL through the selected provider. Resolves the provider at + * call time with the selection rules above; throws {@link WebError} when the + * capability cannot run. A non-2xx response is a result, not a throw. + * @param request - the URL plus retrieval options. + * @param signal - optional cancellation signal forwarded to the provider. + * @returns the retrieval outcome; non-2xx responses resolve descriptively. + */ +async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise +``` + +Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts) + diff --git a/docs/subsystems/web.zh.md b/docs/subsystems/web.zh.md index e2982ba571..0c72e343c1 100644 --- a/docs/subsystems/web.zh.md +++ b/docs/subsystems/web.zh.md @@ -133,3 +133,70 @@ type WebFetchBody = ## 服务 `WebService` 注册搜索与抓取提供方,以 `WEB_DUPLICATE_PROVIDER` 拒绝重复 id,并在执行时以结构化的选择错误解析提供方。本地抓取后端仅接受 HTTP(S)、拒绝凭证、限制重定向次数、字节数、字符数和时间、对每一跳同源重定向重新校验,并解码正文;展示由工具负责。SSRF/私有网络防护尚未实现,因此在能够触及敏感内部目标的环境中,禁止启用 `web_fetch`。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.web` — `WebService` + +The web access service. Registered as `ctx.web` (one instance per context). + +Selection semantics (resolved at execution time, never order-dependent): + +- A configured id that is registered and `available()` → that provider. +- A configured id not registered → `WEB_PROVIDER_CONFIGURED_MISSING`. +- A configured id registered but unavailable → `WEB_PROVIDER_CONFIGURED_UNAVAILABLE`. +- No id configured, exactly one registered usable provider → that provider. +- No id configured, multiple usable providers → `WEB_PROVIDER_AMBIGUOUS`. +- No id configured, no usable provider → `WEB_PROVIDER_UNAVAILABLE`. + +```ts cordis-catalog +/** + * Register a search provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` + * if its id is already registered for search. Returns a disposer; disposed + * with the calling fiber. + * @param provider - the provider; its `id` is the registry key. + * @returns the disposer that unregisters the provider. + */ +registerSearchProvider(provider: WebSearchProvider): () => void + +/** + * Register a fetch provider. Throws {@link WebError} `WEB_DUPLICATE_PROVIDER` + * if its id is already registered for fetch. Returns a disposer; disposed + * with the calling fiber. + * @param provider - the provider; its `id` is the registry key. + * @returns the disposer that unregisters the provider. + */ +registerFetchProvider(provider: WebFetchProvider): () => void + +/** + * Run one search through the selected provider. Resolves the provider at call + * time with the selection rules above; throws {@link WebError} when the + * capability cannot run. The seam enforces `request.maxResults` on the result: + * if the provider over-returns, `sources[]` is truncated and `truncated` set. + * @param request - the query plus result-shaping options. + * @param signal - optional cancellation signal forwarded to the provider. + * @returns the provider's results, capped to `request.maxResults`. + */ +async search(request: WebSearchRequest, signal?: AbortSignal): Promise + +/** + * Retrieve one URL through the selected provider. Resolves the provider at + * call time with the selection rules above; throws {@link WebError} when the + * capability cannot run. A non-2xx response is a result, not a throw. + * @param request - the URL plus retrieval options. + * @param signal - optional cancellation signal forwarded to the provider. + * @returns the retrieval outcome; non-2xx responses resolve descriptively. + */ +async fetch(request: WebFetchRequest, signal?: AbortSignal): Promise +``` + +Source: [`packages/web/web/src/index.ts:74`](../../packages/web/web/src/index.ts) + diff --git a/docs/subsystems/workflow.i18n.yaml b/docs/subsystems/workflow.i18n.yaml index 7f996a1c8e..4f581d8bae 100644 --- a/docs/subsystems/workflow.i18n.yaml +++ b/docs/subsystems/workflow.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write docs/core-data-structures/workflow.md -workflow.md: 8d271b89e71de6f6bef548aa8da61402ef9ada6e -workflow.zh.md: 91a4902bbc004e911c8aa84adb6a4abeda9dd59f +# pnpm run verify-translation-pairing --write docs/subsystems/workflow.md +workflow.md: 53cb8be2c975fba396c3d2023c60fc1da2ed9c8a +workflow.zh.md: 652b68c833f7f20fe72307946d02f89558be2c2b diff --git a/docs/subsystems/workflow.md b/docs/subsystems/workflow.md index 8d271b89e7..53cb8be2c9 100644 --- a/docs/subsystems/workflow.md +++ b/docs/subsystems/workflow.md @@ -129,4 +129,154 @@ Hook misuse inside a script — bad arguments, unknown/deferred `agent()` option ## Events -The `workflow/*` events (`workflow/start`, `workflow/phase`, `workflow/log`, `workflow/agent-start`, `workflow/agent-end`, `workflow/end` — see the [events catalog](../cordis-catalog/events.md)) are **observe-only** emits carrying DATA SNAPSHOTS: every payload starts with `WorkflowRunInfo` (id + meta), never the live `WorkflowRun`, so a subscriber cannot gain `cancel`/`dispose`, and `workflow/end` deliberately omits the result value (a listener observing outcomes must not receive a mutable alias of the caller's result). Every emit is per-listener contained — a throwing subscriber is logged, never propagated, and cannot starve the listeners registered after it — and every listener receives its own payload clone, so mutating it corrupts neither the engine nor other listeners; the containment mirrors `subagent/start`/`subagent/end`. +The `workflow/*` events (`workflow/start`, `workflow/phase`, `workflow/log`, `workflow/agent-start`, `workflow/agent-end`, `workflow/end` — see the [events catalog](#cordis-surface)) are **observe-only** emits carrying DATA SNAPSHOTS: every payload starts with `WorkflowRunInfo` (id + meta), never the live `WorkflowRun`, so a subscriber cannot gain `cancel`/`dispose`, and `workflow/end` deliberately omits the result value (a listener observing outcomes must not receive a mutable alias of the caller's result). Every emit is per-listener contained — a throwing subscriber is logged, never propagated, and cannot starve the listeners registered after it — and every listener receives its own payload clone, so mutating it corrupts neither the engine nor other listeners; the containment mirrors `subagent/start`/`subagent/end`. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.workflows` — `WorkflowService` (abstract seam) + +Workflow execution seam. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles. + +```ts cordis-catalog +/** + * Parse and execute a workflow script. + * @param request - the script, its `args`, the parent agent, and an + * optional cancel signal. + * @returns the live run; its `result` resolves when the script settles. + */ +abstract start(request: WorkflowStartRequest): WorkflowRun +``` + +Source: [`packages/workflow/workflow/src/index.ts:159`](../../packages/workflow/workflow/src/index.ts) + + + +### `workflow/*` events + + + +#### `workflow/agent-end` — emit + +One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`. + +```ts cordis-catalog +/** + * One `agent()` call settled (clean result, child failure, or run + * cancellation). Paired with {@link Events['workflow/agent-start']} by + * `agent.seq`, exactly once per started call on every stop path — on an + * engine termination path (a worker killed past its grace) the end is + * engine-synthesized with outcome `'cancelled'`. + * @param info - the run's identity snapshot. + * @param agent - the call identity plus its outcome. + * @mode emit + */ +'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:81`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/agent-start` — emit + +One `agent()` call established a published child run. Paired with Events['workflow/agent-end'] by `agent.seq`. A call that never receives a published run from the provider emits neither event in this pair. + +```ts cordis-catalog +/** + * One `agent()` call established a published child run. Paired with + * {@link Events['workflow/agent-end']} by `agent.seq`. A call that never + * receives a published run from the provider emits neither + * event in this pair. + * @param info - the run's identity snapshot. + * @param agent - the call's sequence number, label, phase, and child id. + * @mode emit + */ +'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:70`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/end` — emit + +A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start']. + +```ts cordis-catalog +/** + * A workflow run settled (any stop reason). Fired when + * {@link WorkflowRun.result} resolves. Paired with + * {@link Events['workflow/start']}. + * @param info - the run's identity snapshot. + * @param result - the outcome data (stop reason, error, agent count) — + * deliberately WITHOUT the result value (see {@link WorkflowResultInfo}). + * @mode emit + */ +'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:91`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/log` — emit + +The script emitted a narration line (a `log(message)` call). + +```ts cordis-catalog +/** + * The script emitted a narration line (a `log(message)` call). + * @param info - the run's identity snapshot. + * @param message - the logged message, verbatim. + * @mode emit + */ +'workflow/log'(info: WorkflowRunInfo, message: string): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:60`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/phase` — emit + +The script entered a phase (a `phase(title)` call) — progress grouping for observers; no execution semantics. + +```ts cordis-catalog +/** + * The script entered a phase (a `phase(title)` call) — progress grouping + * for observers; no execution semantics. + * @param info - the run's identity snapshot. + * @param title - the phase title, verbatim. + * @mode emit + */ +'workflow/phase'(info: WorkflowRunInfo, title: string): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:53`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/start` — emit + +A workflow run started — the script's meta block validated, the body about to execute. Paired with Events['workflow/end']. + +```ts cordis-catalog +/** + * A workflow run started — the script's meta block validated, the body + * about to execute. Paired with {@link Events['workflow/end']}. + * @param info - the run's identity snapshot (id + meta). + * @mode emit + */ +'workflow/start'(info: WorkflowRunInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:45`](../../packages/workflow/workflow/src/index.ts) + diff --git a/docs/subsystems/workflow.zh.md b/docs/subsystems/workflow.zh.md index 91a4902bbc..652b68c833 100644 --- a/docs/subsystems/workflow.zh.md +++ b/docs/subsystems/workflow.zh.md @@ -129,4 +129,154 @@ interface WorkflowRun { ## 事件 -`workflow/*` 事件(`workflow/start`、`workflow/phase`、`workflow/log`、`workflow/agent-start`、`workflow/agent-end`、`workflow/end`,见[事件目录](../cordis-catalog/events.md))是**仅供观察**的 emit,携带数据快照:每个 payload 以 `WorkflowRunInfo`(id + meta)开头,而非活跃的 `WorkflowRun`,因此订阅者无法获得 `cancel`/`dispose`;`workflow/end` 刻意省略 result value(观察结果的监听器不得收到调用方 result 的可变别名)。每次 emit 对每个监听器隔离:抛出异常的订阅者被记录日志但不传播,不会饿死在它之后注册的监听器;每个监听器收到自己的 payload 克隆,因此修改它既不会损坏引擎也不会影响其他监听器。这种隔离方式与 `subagent/start`/`subagent/end` 一致。 +`workflow/*` 事件(`workflow/start`、`workflow/phase`、`workflow/log`、`workflow/agent-start`、`workflow/agent-end`、`workflow/end`,见[事件目录](#cordis-surface))是**仅供观察**的 emit,携带数据快照:每个 payload 以 `WorkflowRunInfo`(id + meta)开头,而非活跃的 `WorkflowRun`,因此订阅者无法获得 `cancel`/`dispose`;`workflow/end` 刻意省略 result value(观察结果的监听器不得收到调用方 result 的可变别名)。每次 emit 对每个监听器隔离:抛出异常的订阅者被记录日志但不传播,不会饿死在它之后注册的监听器;每个监听器收到自己的 payload 克隆,因此修改它既不会损坏引擎也不会影响其他监听器。这种隔离方式与 `subagent/start`/`subagent/end` 一致。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.workflows` — `WorkflowService` (abstract seam) + +Workflow execution seam. Invalid requests throw before publication; a live run is holder-owned, its result never rejects, cancellation and disposal are bounded, and disposal waits for child cleanup within that bound. Lifecycle listener failures are contained, and `workflow/end` fires exactly once as the result settles. + +```ts cordis-catalog +/** + * Parse and execute a workflow script. + * @param request - the script, its `args`, the parent agent, and an + * optional cancel signal. + * @returns the live run; its `result` resolves when the script settles. + */ +abstract start(request: WorkflowStartRequest): WorkflowRun +``` + +Source: [`packages/workflow/workflow/src/index.ts:159`](../../packages/workflow/workflow/src/index.ts) + + + +### `workflow/*` events + + + +#### `workflow/agent-end` — emit + +One `agent()` call settled (clean result, child failure, or run cancellation). Paired with Events['workflow/agent-start'] by `agent.seq`, exactly once per started call on every stop path — on an engine termination path (a worker killed past its grace) the end is engine-synthesized with outcome `'cancelled'`. + +```ts cordis-catalog +/** + * One `agent()` call settled (clean result, child failure, or run + * cancellation). Paired with {@link Events['workflow/agent-start']} by + * `agent.seq`, exactly once per started call on every stop path — on an + * engine termination path (a worker killed past its grace) the end is + * engine-synthesized with outcome `'cancelled'`. + * @param info - the run's identity snapshot. + * @param agent - the call identity plus its outcome. + * @mode emit + */ +'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:81`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/agent-start` — emit + +One `agent()` call established a published child run. Paired with Events['workflow/agent-end'] by `agent.seq`. A call that never receives a published run from the provider emits neither event in this pair. + +```ts cordis-catalog +/** + * One `agent()` call established a published child run. Paired with + * {@link Events['workflow/agent-end']} by `agent.seq`. A call that never + * receives a published run from the provider emits neither + * event in this pair. + * @param info - the run's identity snapshot. + * @param agent - the call's sequence number, label, phase, and child id. + * @mode emit + */ +'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:70`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/end` — emit + +A workflow run settled (any stop reason). Fired when WorkflowRun.result resolves. Paired with Events['workflow/start']. + +```ts cordis-catalog +/** + * A workflow run settled (any stop reason). Fired when + * {@link WorkflowRun.result} resolves. Paired with + * {@link Events['workflow/start']}. + * @param info - the run's identity snapshot. + * @param result - the outcome data (stop reason, error, agent count) — + * deliberately WITHOUT the result value (see {@link WorkflowResultInfo}). + * @mode emit + */ +'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:91`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/log` — emit + +The script emitted a narration line (a `log(message)` call). + +```ts cordis-catalog +/** + * The script emitted a narration line (a `log(message)` call). + * @param info - the run's identity snapshot. + * @param message - the logged message, verbatim. + * @mode emit + */ +'workflow/log'(info: WorkflowRunInfo, message: string): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:60`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/phase` — emit + +The script entered a phase (a `phase(title)` call) — progress grouping for observers; no execution semantics. + +```ts cordis-catalog +/** + * The script entered a phase (a `phase(title)` call) — progress grouping + * for observers; no execution semantics. + * @param info - the run's identity snapshot. + * @param title - the phase title, verbatim. + * @mode emit + */ +'workflow/phase'(info: WorkflowRunInfo, title: string): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:53`](../../packages/workflow/workflow/src/index.ts) + + + +#### `workflow/start` — emit + +A workflow run started — the script's meta block validated, the body about to execute. Paired with Events['workflow/end']. + +```ts cordis-catalog +/** + * A workflow run started — the script's meta block validated, the body + * about to execute. Paired with {@link Events['workflow/end']}. + * @param info - the run's identity snapshot (id + meta). + * @mode emit + */ +'workflow/start'(info: WorkflowRunInfo): void +``` + +Source: [`packages/workflow/workflow/src/index.ts:45`](../../packages/workflow/workflow/src/index.ts) + diff --git a/docs/subsystems/workspace.i18n.yaml b/docs/subsystems/workspace.i18n.yaml index 6a6488206c..b2c2248876 100644 --- a/docs/subsystems/workspace.i18n.yaml +++ b/docs/subsystems/workspace.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/workspace.md -workspace.md: 5614631e828e51f3b515ae4af17451bd9eda1dbb -workspace.zh.md: 11e8f0207a8db8b7612d6672cfe744523490aef7 +workspace.md: ca088a2091a7f47a3d52992fec13fae44061a608 +workspace.zh.md: e414c759a043f934e1a8b5d89c7a3b6101bbb6f4 diff --git a/docs/subsystems/workspace.md b/docs/subsystems/workspace.md index 5614631e82..ca088a2091 100644 --- a/docs/subsystems/workspace.md +++ b/docs/subsystems/workspace.md @@ -117,10 +117,103 @@ Ownership truth is the record's ordered `sessionIds`, never derived from session ## The registry: `ctx.workspace` -`WorkspaceRegistry` ([signatures](../cordis-catalog/services.md#ctxworkspace--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption. +`WorkspaceRegistry` ([signatures](#ctxworkspace--workspaceregistry)) owns registration and resolution. `create(path, title?)` canonicalizes the path, rejects a nonexistent path (the original `ENOENT`) or a non-directory, returns the existing entity unchanged when the canonical path is already owned, and otherwise creates a record with `title ?? basename(path)` prepended to the durable registry order — a new record cannot duplicate an existing display title (`WorkspaceNameConflictError`). `get(id)` and the ordered `list()` are synchronous cache reads; `resolveByPath(path)` applies the same realpath canon without creating. `delete(id)` removes only the registration, order entry, and session account — the directory, user files, live sessions, and persisted logs are never touched, so those sessions become Ungrouped ([decision](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md)); unknown ids return `false`. Create and delete persist a pending-mutation marker before their two writes (record + order) can diverge; startup resolves exactly the marked mutation — by deleting the marked table row, which completes an interrupted delete and rolls back an interrupted create (the registration is re-creatable, so rollback is the safe direction) — and an unmarked order/table mismatch fails loud as corruption. Sessions get their cwd at create time from whoever creates them, not from this registry — the API gateway resolves a new session's cwd from the chosen workspace's `path` (falling back to an explicit or default cwd), creates the session so the cwd lands in its immutable [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log), then calls `attachSession`, which re-validates that stored header cwd against the workspace path. On the first successful start, the registry bootstraps history from persisted headers alone (`id`, `cwd`, `createdAt` — never event bodies), grouping sessions with a valid canonical cwd into per-directory workspaces, newest first; the initialized marker is written last so an interrupted bootstrap resumes safely. The bootstrap is one-time: cwd-less legacy sessions stay Ungrouped, and sessions created afterwards join a workspace only through `attachSession`. ## Consumers [dsh-host-apiproxy](../../packages/host/apiproxy) is the product consumer: it serves workspace CRUD to GUI clients over `ctx.workspace` and performs the create-session-then-attach flow above. [dsh-workspace-context](../../packages/context/workspace-context) is **not** a consumer despite the name: it discovers AGENTS.md-style instruction files under an agent's own cwd and never touches `ctx.workspace` — the shared word refers to the user's working directory, not to this registry's entities. + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.directoryPicker` — `DirectoryPicker` (abstract seam) + +Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls. + +```ts cordis-catalog +/** + * The backend's interaction capability. + * @returns the discriminated capability consumers switch on. + */ +abstract capability(): DirectoryPickerCapability +``` + +Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts) + + + +### `ctx.workspace` — `WorkspaceRegistry` + +Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker. + +```ts cordis-catalog +/** + * Create or reuse a workspace for an existing directory. The path is + * canonicalized through `fs.realpath`; a nonexistent path rejects with the + * original error and a non-directory rejects. Repeated calls for the same + * canonical path return the existing entity without changing its title. + * A newly created workspace is prepended to the durable registry order. + * Different canonical paths may share a display title. + * @param path - Existing directory to own, in any path spelling. + * @param title - Display title used only when a new record is created. + * @returns the existing or newly durable workspace. + */ +async create(path: string, title?: string): Promise + +/** + * Look up a workspace by id. + * @param id - Workspace id. + * @returns the workspace, or `undefined` when unknown. + */ +get(id: WorkspaceId): Workspace | undefined + +/** + * Synchronous workspace projection in durable registry order. Every + * entity's `sessionIds` getter is already filtered by the startup/live + * canonical-cwd header index; this method performs no persistence reads. + * @returns a fresh ordered array of workspace entities. + */ +list(): Workspace[] + +/** + * Delete one workspace registration while retaining its directory and every + * session log. The durable order is updated before the table deletion; a + * failed table write restores the prior order and keeps the entity + * published. Unknown ids are an idempotent no-op for domain callers. + * @param id - Workspace registration to remove. + * @returns `true` when a record was deleted, `false` when it was unknown. + */ +delete(id: WorkspaceId): Promise + +/** + * Archive one session durably. The session must exist (live or in session + * persistence); its workspace accounting — or lack of one — is irrelevant. + * An already archived id resolves without writing. + * @param sessionId - The session to archive. + * @returns resolution after durability. + */ +archiveSession(sessionId: SessionId): Promise + +/** + * Resolve by canonical directory path without creating or mutating a + * workspace. A missing path rejects during `realpath`; an existing unowned + * directory returns `undefined`. + * @param path - Existing directory path in any spelling. + * @returns the workspace owning the canonical path, when one exists. + */ +async resolveByPath(path: string): Promise +``` + +Types: [SessionId](core.md) + +Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts) + diff --git a/docs/subsystems/workspace.zh.md b/docs/subsystems/workspace.zh.md index 11e8f0207a..e414c759a0 100644 --- a/docs/subsystems/workspace.zh.md +++ b/docs/subsystems/workspace.zh.md @@ -117,10 +117,103 @@ interface Workspace { ## 注册表:`ctx.workspace` -`WorkspaceRegistry`([签名](../cordis-catalog/services.md#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。 +`WorkspaceRegistry`([签名](#ctxworkspace--workspaceregistry))拥有注册与解析。`create(path, title?)` 规范化路径,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? basename(path)` 的记录并前插到持久的注册表顺序中——新记录不得与既有显示标题重复(`WorkspaceNameConflictError`)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。 会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。 ## 消费方 [dsh-host-apiproxy](../../packages/host/apiproxy) 是产品消费方:它经 `ctx.workspace` 向 GUI 客户端提供工作区的 CRUD,并执行上文「先建会话再 attach」的流程。[dsh-workspace-context](../../packages/context/workspace-context) 尽管名字如此,却**不是**消费方:它在 agent 自己的 cwd 下发现 AGENTS.md 风格的指令文件,从不触碰 `ctx.workspace`——两者共用的这个词指的是用户的工作目录,而非本注册表的实体。 + + + + + +## Cordis surface + +Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md). + + + +### `ctx.directoryPicker` — `DirectoryPicker` (abstract seam) + +Abstract directory-picking service. Subclass, implement `capability()`, and load the subclass as a plugin — it registers as `ctx.directoryPicker` (one implementation per context; loading a second throws, cordis' standard duplicate-service behavior). The capability object must be stable for the service lifetime: consumers may capture it across calls. + +```ts cordis-catalog +/** + * The backend's interaction capability. + * @returns the discriminated capability consumers switch on. + */ +abstract capability(): DirectoryPickerCapability +``` + +Source: [`packages/host/directory-picker/src/index.ts:131`](../../packages/host/directory-picker/src/index.ts) + + + +### `ctx.workspace` — `WorkspaceRegistry` + +Durable workspace registry. Startup waits for `sessionPersistence`, builds one canonical-cwd header index, and completes the one-time history bootstrap before the service becomes active. The persistence dependency is mandatory so an unavailable peer can never be mistaken for an empty history and commit the initialized marker. + +```ts cordis-catalog +/** + * Create or reuse a workspace for an existing directory. The path is + * canonicalized through `fs.realpath`; a nonexistent path rejects with the + * original error and a non-directory rejects. Repeated calls for the same + * canonical path return the existing entity without changing its title. + * A newly created workspace is prepended to the durable registry order. + * Different canonical paths may share a display title. + * @param path - Existing directory to own, in any path spelling. + * @param title - Display title used only when a new record is created. + * @returns the existing or newly durable workspace. + */ +async create(path: string, title?: string): Promise + +/** + * Look up a workspace by id. + * @param id - Workspace id. + * @returns the workspace, or `undefined` when unknown. + */ +get(id: WorkspaceId): Workspace | undefined + +/** + * Synchronous workspace projection in durable registry order. Every + * entity's `sessionIds` getter is already filtered by the startup/live + * canonical-cwd header index; this method performs no persistence reads. + * @returns a fresh ordered array of workspace entities. + */ +list(): Workspace[] + +/** + * Delete one workspace registration while retaining its directory and every + * session log. The durable order is updated before the table deletion; a + * failed table write restores the prior order and keeps the entity + * published. Unknown ids are an idempotent no-op for domain callers. + * @param id - Workspace registration to remove. + * @returns `true` when a record was deleted, `false` when it was unknown. + */ +delete(id: WorkspaceId): Promise + +/** + * Archive one session durably. The session must exist (live or in session + * persistence); its workspace accounting — or lack of one — is irrelevant. + * An already archived id resolves without writing. + * @param sessionId - The session to archive. + * @returns resolution after durability. + */ +archiveSession(sessionId: SessionId): Promise + +/** + * Resolve by canonical directory path without creating or mutating a + * workspace. A missing path rejects during `realpath`; an existing unowned + * directory returns `undefined`. + * @param path - Existing directory path in any spelling. + * @returns the workspace owning the canonical path, when one exists. + */ +async resolveByPath(path: string): Promise +``` + +Types: [SessionId](core.md) + +Source: [`packages/workspace/workspace/src/index.ts:81`](../../packages/workspace/workspace/src/index.ts) + diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index a100469102..8986847d1e 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -3,7 +3,7 @@ # Tool Schema Catalog -Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [subsystems/](subsystems/core.md) (the types those signatures move) — this page is the *tools* the agent is offered. +Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [subsystem pages](subsystems/core.md) (the types plus each page's generated `cordis-surface` wiring region) — this page is the *tools* the agent is offered. This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator's boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog Agent Note](../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md). diff --git a/docs/user/develop/framework/events.i18n.yaml b/docs/user/develop/framework/events.i18n.yaml index 769556da41..37dd601646 100644 --- a/docs/user/develop/framework/events.i18n.yaml +++ b/docs/user/develop/framework/events.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/user/develop/framework/events.md -events.md: fcbdb5f39bf2078032affbc6469f7eecc795d3ba -events.zh.md: 1979e0bc1dbb71e46f50a051dacee8ae3a1172a7 +events.md: c70150b4d9070063513021e420f253ff6d4e5de1 +events.zh.md: 813dc3631e0c776ad9c8763db65eebc3f2167578 diff --git a/docs/user/develop/framework/events.md b/docs/user/develop/framework/events.md index fcbdb5f39b..c70150b4d9 100644 --- a/docs/user/develop/framework/events.md +++ b/docs/user/develop/framework/events.md @@ -101,7 +101,7 @@ declare module 'cordis' { ## Cordis events and session records -Harness Cordis events use `namespace/action` names, including `agent/pre-step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated [event catalog](../../../cordis-catalog/events.md) records complete signatures and modes. +Harness Cordis events use `namespace/action` names, including `agent/step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated `cordis-surface` regions on the [subsystem pages](../../../subsystems/core.md) record complete signatures and modes. `turn/*`, `step/*`, `tool/call`, `tool/result`, and `compact/*` are durable session-event types, not same-named Cordis events. To observe them, listen to `session/event` and inspect `event.type`. diff --git a/docs/user/develop/framework/events.zh.md b/docs/user/develop/framework/events.zh.md index 1979e0bc1d..813dc3631e 100644 --- a/docs/user/develop/framework/events.zh.md +++ b/docs/user/develop/framework/events.zh.md @@ -101,7 +101,7 @@ declare module 'cordis' { ## Cordis 事件与会话记录 -Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-step`、`agent/request`、`agent/request-error`、`tools/result` 和 `session/event`。完整签名与触发模式见[Events 目录](../../../cordis-catalog/events.md)。 +Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/step`、`agent/request`、`agent/request-error`、`tools/result` 和 `session/event`。完整签名与触发模式见[子系统页面](../../../subsystems/core.md)上生成的 `cordis-surface` 区块。 `turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compact/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。 diff --git a/docs/user/develop/framework/service.i18n.yaml b/docs/user/develop/framework/service.i18n.yaml index ade6b8dc08..7cb2f4ff88 100644 --- a/docs/user/develop/framework/service.i18n.yaml +++ b/docs/user/develop/framework/service.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/user/develop/framework/service.md -service.md: 1bf28cb3c7dfdfbd6d0babfa3b1688ac65eea01e -service.zh.md: 78b9887a734b235bcff1d94a623e37cfeb2f41e3 +service.md: 040b1388cc431c30045e05f7d372ab5885bb3f9d +service.zh.md: 0786b684c1688440a24cc729288835ad636f8ff7 diff --git a/docs/user/develop/framework/service.md b/docs/user/develop/framework/service.md index 1bf28cb3c7..040b1388cc 100644 --- a/docs/user/develop/framework/service.md +++ b/docs/user/develop/framework/service.md @@ -140,7 +140,7 @@ This prevents a plugin from calling a service that no longer exists. ## Built-in Harness services -The repository generates the service names, public methods, and source locations in the [service catalog](../../../cordis-catalog/services.md). Use that catalog and the service's TypeScript interface while developing a plugin; do not maintain a second static list. +The repository generates the service names, public methods, and source locations into each service's [subsystem page](../../../subsystems/core.md). Use those generated regions and the service's TypeScript interface while developing a plugin; do not maintain a second static list. ## Next steps diff --git a/docs/user/develop/framework/service.zh.md b/docs/user/develop/framework/service.zh.md index 78b9887a73..0786b684c1 100644 --- a/docs/user/develop/framework/service.zh.md +++ b/docs/user/develop/framework/service.zh.md @@ -140,7 +140,7 @@ export function apply(ctx: Context) { ## Harness 内置服务 -仓库会自动生成[服务目录](../../../cordis-catalog/services.md),其中包含服务名、公开方法和源码位置。开发插件时应以该目录和服务的 TypeScript 接口为准,不要维护另一份静态清单。 +服务名、公开方法和源码位置由仓库自动生成到各服务的[子系统页面](../../../subsystems/core.md)。开发插件时应以这些生成区块和服务接口的 TypeScript 类型为准,不要复制一份静态清单。 ## 下一步 diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 2f165e76e4..fb18353290 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/agent-loop/README.md -README.md: 2fdc60086bebc924089b5b8bd12f4b4456b1ead5 -README.zh.md: df0ba44ec5b010666737c0568bc5eeb220ff0d96 +README.md: 81bc4411977f7335defef5af82d4a67a740b2ed5 +README.zh.md: c190171513e17777ebaadda0d279487876a412b4 diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 2fdc60086b..81bc441197 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -74,7 +74,7 @@ Within a step, exclusive calls form barriers; parallel-safe calls use a bounded ### What belongs to plugins Everything that goes beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy: -- Hooks and policy: the relevant `agent/*` checkpoints plus the guarded `tools/pre-execute` → `tools/execute` → `tools/post-execute` → definition-owned `finalizeContent` → `tools/result` pipeline; exact event signatures and modes live in the [generated event catalog](../../../docs/cordis-catalog/events.md) +- Hooks and policy: the relevant `agent/*` checkpoints plus the guarded `tools/pre-execute` → `tools/execute` → `tools/post-execute` → definition-owned `finalizeContent` → `tools/result` pipeline; exact event signatures and modes live in the generated regions of [core.md](../../../docs/subsystems/core.md#cordis-surface) and [tools.md](../../../docs/subsystems/tools.md#cordis-surface) - Compaction: pressure on `agent/pre-step`; canonical overflow repair on `agent/request-error` - Model-request recovery: `dsh-llm-retry` records and waits exact-provider normal or unbounded backoff on `agent/request-error`, emits non-surface `llm/retry` status, then returns a retry action - Sandbox, permission, plan mode: `tools/pre-execute` for extensible deny/ask, `tools.guard()` for monotonic owner policy, `tools/post-execute` for result decisions, and `tools/result` for final observation diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index df0ba44ec5..c190171513 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -74,7 +74,7 @@ interface Config { ### 插件负责的内容 超出「调用模型、运行工具、重复」的所有内容,都属于监听事件分类体系的插件: -- 钩子与策略:相关的 `agent/*` 检查点,加上受守卫保护的 `tools/pre-execute` → `tools/execute` → `tools/post-execute` → 定义拥有的 `finalizeContent` → `tools/result` 流水线;确切事件签名与 mode 位于生成的[事件目录](../../../docs/cordis-catalog/events.md) +- 钩子与策略:相关的 `agent/*` 检查点,加上受守卫保护的 `tools/pre-execute` → `tools/execute` → `tools/post-execute` → 定义拥有的 `finalizeContent` → `tools/result` 流水线;确切事件签名与 mode 位于 [core.md](../../../docs/subsystems/core.md#cordis-surface) 与 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块 - 压缩(compaction):在 `agent/pre-step` 上观测压力;在 `agent/request-error` 上进行规范的溢出修复 - 模型请求恢复:`dsh-llm-retry` 在 `agent/request-error` 上记录并等待按确切提供方配置的 normal 或无界退避,发出不进入表层的 `llm/retry` 状态,然后返回重试动作 - 沙箱、权限、计划模式:使用 `tools/pre-execute` 提供可扩展的拒绝/询问,使用 `tools.guard()` 提供单调拥有方策略,使用 `tools/post-execute` 处理结果决定,并使用 `tools/result` 进行最终观测 diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 5c03669baf..4c6009d3b0 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/agent/README.md -README.md: 2a69ab380eaad3929e27039582807037969eba64 -README.zh.md: 176f3f75cf0f6e3309b2f5d34afb4d562105608e +README.md: 3a3bdf6a4b3bdc5bfef250495e84b7d90b003822 +README.zh.md: 8164829e18ddd89fd84c0c2b5e033fd69241b9aa diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 2a69ab380e..3a3bdf6a4b 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -46,7 +46,7 @@ Agent *creation* is provided by the plugin implementing `AgentFactory` (`dsh-age ### Live events -`dsh-agent` declares the live `agent/*` coordination vocabulary so plugins do not depend on the concrete loop. Exact signatures, dispatch modes, scope-filtering rules, and payload contracts live in the generated [Cordis event catalog](../../../docs/cordis-catalog/events.md); the [architecture turn flow](../../../docs/architecture.md#turn-flow) shows their order relative to durable session events. +`dsh-agent` declares the live `agent/*` coordination vocabulary so plugins do not depend on the concrete loop. Exact signatures, dispatch modes, scope-filtering rules, and payload contracts live in the generated region of [core.md](../../../docs/subsystems/core.md#cordis-surface); the [architecture turn flow](../../../docs/architecture.md#turn-flow) shows their order relative to durable session events. The lifecycle edges have two important local caveats. `agent/created` runs after scoped setup and after both session and agent registry entries exist. Setup is trusted composition-only code; the immediately following non-vetoing `agent/session-start` notification is the first supported startup injection point. `agent/disposed` always means the exact agent has left the registry. AgentLoop emits it after its driver is quiescent, while ordered teardown may still be detaching the session and unwinding the scope; custom agents registered directly own any stronger driver-ordering contract themselves. diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index 176f3f75cf..8164829e18 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -46,7 +46,7 @@ Agent *创建* 由实现 `AgentFactory` 的插件(`dsh-agent-loop`)提供, ### 实时事件 -`dsh-agent` 声明实时 `agent/*` 协调词汇,使插件不必依赖具体循环。确切签名、分发 mode、作用域筛选规则与 payload 契约位于生成的 [Cordis 事件目录](../../../docs/cordis-catalog/events.md);[架构轮次流](../../../docs/architecture.md#turn-flow) 展示它们与持久会话事件的相对顺序。 +`dsh-agent` 声明实时 `agent/*` 协调词汇,使插件不必依赖具体循环。确切签名、分发 mode、作用域筛选规则与 payload 契约位于 [core.md](../../../docs/subsystems/core.md#cordis-surface) 的生成区块;[架构轮次流](../../../docs/architecture.md#turn-flow) 展示它们与持久会话事件的相对顺序。 生命周期边有两个重要的本地注意事项。`agent/created` 在作用域 setup 之后、会话与 agent 注册表条目都存在之后运行。Setup 是受信任、仅用于组合的代码;紧随其后且不可 veto 的 `agent/session-start` 通知是第一个受支持的启动注入点。`agent/disposed` 始终表示确切 agent 已离开注册表。AgentLoop 在其驱动器完全停稳后发出该事件,而有序 teardown 此时可能仍在分离会话并撤销作用域;直接注册的自定义 agent 自行拥有任何更强的驱动器顺序契约。 diff --git a/packages/core/session/README.i18n.yaml b/packages/core/session/README.i18n.yaml index c30b37e549..8b1e462166 100644 --- a/packages/core/session/README.i18n.yaml +++ b/packages/core/session/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/session/README.md -README.md: 07ee53006b390e5ad3979502b2f1aa24f5a6ce7a -README.zh.md: d4a90de10b92a489f8f575b926d758b45d8f5051 +README.md: 00e007ee6064f485bf80dfda0227ab6e834f6414 +README.zh.md: 5f06fee5d50bc2c9dc333874a8354fbf19d5248b diff --git a/packages/core/session/README.md b/packages/core/session/README.md index 07ee53006b..00e007ee60 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -30,7 +30,7 @@ Use the split lifecycle only when teardown must be ordered with another resource ### Live service events -The store pairs announced creation with disposal, publishes post-commit append notifications with per-listener containment, and provides an awaited durability checkpoint. Exact signatures and scope behavior live in the generated [event catalog](../../../docs/cordis-catalog/events.md); payloads live in the [persistence catalog](../../../docs/persistence-catalog.md). +The store pairs announced creation with disposal, publishes post-commit append notifications with per-listener containment, and provides an awaited durability checkpoint. Exact signatures and scope behavior live in the generated region of [session.md](../../../docs/subsystems/session.md#cordis-surface); payloads live in the [persistence catalog](../../../docs/persistence-catalog.md). ### Class: `Session` diff --git a/packages/core/session/README.zh.md b/packages/core/session/README.zh.md index d4a90de10b..5f06fee5d5 100644 --- a/packages/core/session/README.zh.md +++ b/packages/core/session/README.zh.md @@ -30,7 +30,7 @@ ### 实时服务事件 -会话存储会将已通知的创建与释放配对,在提交后发布追加通知并逐个监听器收容失败,同时提供受等待的持久性检查点。确切签名和作用域行为见生成的[事件目录](../../../docs/cordis-catalog/events.md);载荷见[持久化目录](../../../docs/persistence-catalog.md)。 +会话存储会将已通知的创建与释放配对,在提交后发布追加通知并逐个监听器收容失败,同时提供受等待的持久性检查点。确切签名和作用域行为见 [session.md](../../../docs/subsystems/session.md#cordis-surface) 的生成区块;载荷见[持久化目录](../../../docs/persistence-catalog.md)。 ### 类:`Session` diff --git a/packages/core/system-prompt/README.i18n.yaml b/packages/core/system-prompt/README.i18n.yaml index 36f166dd86..c94d1722dc 100644 --- a/packages/core/system-prompt/README.i18n.yaml +++ b/packages/core/system-prompt/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/system-prompt/README.md -README.md: 23bc0e8177ad2a778df9522e254bfd5e03a9871f -README.zh.md: b442239a50d539a8f079aa692c433d9defe57295 +README.md: e98c45a8829a945500ef5282d84e1904b31c68bf +README.zh.md: 5b9e2feaf82866a52cd8197ff5e800decdf3ee7e diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index 23bc0e8177..e98c45a882 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -23,7 +23,7 @@ System prompt assembly registry. Plugins contribute ordered sections, tool schem ### Live events -`system-prompt/assemble` is authoritative; listeners that replace entries must preserve any active Code Mode or structured-output protocol. Use [`ToolRegistry.restrict()`](../tools/README.md) when filtering must stay aligned across presentation, lookup, and execution. Registry-change notifications are unfiltered. The generated [event catalog](../../../docs/cordis-catalog/events.md) owns signatures and dispatch contracts. +`system-prompt/assemble` is authoritative; listeners that replace entries must preserve any active Code Mode or structured-output protocol. Use [`ToolRegistry.restrict()`](../tools/README.md) when filtering must stay aligned across presentation, lookup, and execution. Registry-change notifications are unfiltered. The generated region of [system-prompt.md](../../../docs/subsystems/system-prompt.md#cordis-surface) owns signatures and dispatch contracts. ### Key types diff --git a/packages/core/system-prompt/README.zh.md b/packages/core/system-prompt/README.zh.md index b442239a50..5b9e2feaf8 100644 --- a/packages/core/system-prompt/README.zh.md +++ b/packages/core/system-prompt/README.zh.md @@ -23,7 +23,7 @@ ### 实时事件 -`system-prompt/assemble` 是权威来源;替换条目的监听器必须保留任何活动 Code Mode 或结构化输出协议。筛选需要在呈现、查找与执行之间保持一致时,应使用 [`ToolRegistry.restrict()`](../tools/README.md)。注册表变更通知不经过筛选。生成的[事件目录](../../../docs/cordis-catalog/events.md) 拥有签名与分发契约。 +`system-prompt/assemble` 是权威来源;替换条目的监听器必须保留任何活动 Code Mode 或结构化输出协议。筛选需要在呈现、查找与执行之间保持一致时,应使用 [`ToolRegistry.restrict()`](../tools/README.md)。注册表变更通知不经过筛选。[system-prompt.md](../../../docs/subsystems/system-prompt.md#cordis-surface) 的生成区块拥有签名与分发契约。 ### 关键类型 diff --git a/packages/core/tools/README.i18n.yaml b/packages/core/tools/README.i18n.yaml index e8157eed7e..b19e86ce84 100644 --- a/packages/core/tools/README.i18n.yaml +++ b/packages/core/tools/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/tools/README.md -README.md: fd5cb85540e42e62de28f96bf59890da9561f51e -README.zh.md: 5911b25b1d77710077063a643a29e500e4da8e95 +README.md: f3f25f908a4ba8016d38f7777fe72691dba4afb1 +README.zh.md: 796ee8e1aa8de88f979d825df5e0b4f39ab7bcf1 diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index fd5cb85540..f3f25f908a 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -35,7 +35,7 @@ Cancellation is cooperative and quiescent. Every typed invocation supplies a cal ### Live events -The live registry pipeline has three transformable waterfalls, then the definition-owned content finalizer, then the observe-only `tools/result` boundary; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure-containment contracts live in the generated [Cordis event catalog](../../../docs/cordis-catalog/events.md), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live; the similarly named `tool/result` is the durable session event the agent loop appends afterwards. +The live registry pipeline has three transformable waterfalls, then the definition-owned content finalizer, then the observe-only `tools/result` boundary; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure-containment contracts live in the generated region of [tools.md](../../../docs/subsystems/tools.md#cordis-surface), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live; the similarly named `tool/result` is the durable session event the agent loop appends afterwards. ### Key types @@ -56,7 +56,7 @@ The live registry pipeline has three transformable waterfalls, then the definiti - `tools/pre-execute` is the reorderable allow/deny/ask gate; `ctx.tools.guard()` adds monotonic owner policy after it. - `tools/execute` wraps normalized canonical dispatch for timeout, retry, or metrics. Wrappers may replace only the operational signal; a wrapper-authored success is normalized through the resolved tool's output declaration. Canonical-result provenance belongs to one immutable dispatch token, so a cached result from another call or tool is revalidated under the active declaration. - `tools/post-execute` may replace presentation content, replace the canonical value, block with feedback, or attach ordered contexts. A definition's optional `finalizeContent` then owns its last content-only invariant across normal results and outer pipeline failures; `tools/result` observes the immutable final outcome. Content replacement is not a confidentiality boundary: block or replace the value when programmatic consumers must not receive it. -- Exact signatures and ordering live in the generated [event catalog](../../../docs/cordis-catalog/events.md) and [pipeline](../../../docs/tool-execution-pipeline.md). +- Exact signatures and ordering live in the generated region of [tools.md](../../../docs/subsystems/tools.md#cordis-surface) and [pipeline](../../../docs/tool-execution-pipeline.md). - MCP servers: one plugin per server, discover tools, call `ctx.tools.register()` with the server's schemas. ### Typed tool parameter schemas diff --git a/packages/core/tools/README.zh.md b/packages/core/tools/README.zh.md index 5911b25b1d..796ee8e1aa 100644 --- a/packages/core/tools/README.zh.md +++ b/packages/core/tools/README.zh.md @@ -35,7 +35,7 @@ tools: ### 实时事件 -实时注册表流水线先经过 3 个可变换的 waterfall,再经过由定义拥有的内容终结器,最后到达仅观测的 `tools/result` 边界;注册表变更有意作为不过滤的共享状态通知。确切签名、分发模式、作用域筛选和故障收容契约位于生成的 [Cordis 事件目录](../../../docs/cordis-catalog/events.md),完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。 +实时注册表流水线先经过 3 个可变换的 waterfall,再经过由定义拥有的内容终结器,最后到达仅观测的 `tools/result` 边界;注册表变更有意作为不过滤的共享状态通知。确切签名、分发 mode、作用域筛选和故障收容契约位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块,完整顺序则在生成的[工具执行流水线](../../../docs/tool-execution-pipeline.md)中可视化。`tools/result` 是实时事件;名称相近的 `tool/result` 是 agent loop 随后追加的持久会话事件。 ### 关键类型 @@ -56,7 +56,7 @@ tools: - `tools/pre-execute` 是可重排的允许/拒绝/询问门禁;`ctx.tools.guard()` 在其后添加单调的拥有方策略。 - `tools/execute` 会环绕包装规范化后的规范分发,以支持超时、重试或指标采集。包装层只能替换操作信号;包装层生成的成功结果会根据已解析工具的输出声明进行规范化。规范结果的来源属于一个不可变分发 token,因此,来自其他调用或工具的缓存结果会根据当前声明重新验证。 - `tools/post-execute` 可以替换呈现内容、替换规范值、通过反馈阻止,或附加有序上下文。随后,定义可选的 `finalizeContent` 会在普通结果和外层流水线失败中维护其最终、仅涉及内容的不变式;`tools/result` 观测不可变的最终结果。内容替换不是保密边界:当编程消费方不得接收某个值时,应阻止或替换该值。 -- 确切签名与顺序位于生成的[事件目录](../../../docs/cordis-catalog/events.md)和[流水线](../../../docs/tool-execution-pipeline.md)中。 +- 确切签名与顺序位于 [tools.md](../../../docs/subsystems/tools.md#cordis-surface) 的生成区块和[流水线](../../../docs/tool-execution-pipeline.md)中。 - MCP 服务器:每个服务器使用一个插件;发现工具后,使用服务器的 schema 调用 `ctx.tools.register()`。 ### 类型化工具参数 schema diff --git a/packages/fs/fs/README.i18n.yaml b/packages/fs/fs/README.i18n.yaml index d9b5f0ebc0..d9b0010670 100644 --- a/packages/fs/fs/README.i18n.yaml +++ b/packages/fs/fs/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/fs/README.md -README.md: bf1dd1c1eb65146258cd64e450749845522e7057 -README.zh.md: f3fcc0c3794b972233dc418e93bdd80b1cc8570a +README.md: 5c58fc476b9b11a83bbe0d6c33ac782d94d6ffbc +README.zh.md: 97d7104f3c4c0beca408397f9cb1cead39b0a76e diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index bf1dd1c1eb..5c58fc476b 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -37,7 +37,7 @@ The mutation runs inside the backend's per-target lock either way, so an uncondi ## The `fs/*` policy events -This package declares three events (see the generated [events catalog](../../../docs/cordis-catalog/events.md)) so the emitter (`@deepseek-ai/dsh-tool-fs`) and the policy listener (`@deepseek-ai/dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. `fs/write-intent` and `fs/edit-intent` are single-slot decision waterfalls (the listener fully decides, never calling `next()`); `fs/observed` is a fire-and-forget recording event. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. +This package declares three events (see the generated region of [filesystem.md](../../../docs/subsystems/filesystem.md#cordis-surface)) so the emitter (`@deepseek-ai/dsh-tool-fs`) and the policy listener (`@deepseek-ai/dsh-fs-policy`) share a vocabulary without the emitter depending on the policy plugin. `fs/write-intent` and `fs/edit-intent` are single-slot decision waterfalls (the listener fully decides, never calling `next()`); `fs/observed` is a fire-and-forget recording event. They carry only `dsh-fs` vocabulary plus an opaque `object` actor — no model-facing concepts and no agent/session owner structure. ## A provider seam, not the policy layer diff --git a/packages/fs/fs/README.zh.md b/packages/fs/fs/README.zh.md index f3fcc0c379..97d7104f3c 100644 --- a/packages/fs/fs/README.zh.md +++ b/packages/fs/fs/README.zh.md @@ -37,7 +37,7 @@ ## `fs/*` 政策事件 -本包声明三个事件(见已生成的[事件目录](../../../docs/cordis-catalog/events.md)),使发出方(`@deepseek-ai/dsh-tool-fs`)和政策监听器(`@deepseek-ai/dsh-fs-policy`)共享词汇,而无需让发出方依赖政策插件。`fs/write-intent` 和 `fs/edit-intent` 是单槽决策 waterfall(监听器完整决策,绝不调用 `next()`);`fs/observed` 是发后即忘的记录事件。它们只携带 `dsh-fs` 词汇和一个不透明 `object` 参与者,不含面向模型的概念或 agent(智能体)/会话所有者结构。 +本包声明三个事件(见 [filesystem.md](../../../docs/subsystems/filesystem.md#cordis-surface) 的生成区块),使发出方(`@deepseek-ai/dsh-tool-fs`)和政策监听器(`@deepseek-ai/dsh-fs-policy`)共享词汇,而无需让发出方依赖政策插件。`fs/write-intent` 和 `fs/edit-intent` 是单槽决策 waterfall(监听器完整决策,绝不调用 `next()`);`fs/observed` 是发后即忘的记录事件。它们只携带 `dsh-fs` 词汇和一个不透明 `object` 参与者,不含面向模型的概念或 agent(智能体)/会话所有者结构。 ## 提供方 seam,不是政策层 diff --git a/packages/goal/goal/README.i18n.yaml b/packages/goal/goal/README.i18n.yaml index 6362a6d1e9..d8fbf8fc29 100644 --- a/packages/goal/goal/README.i18n.yaml +++ b/packages/goal/goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/goal/README.md -README.md: fc2fd8791096bcc9077ef7972da86b0e07c6f18e -README.zh.md: 311ec1d03436cc45bbea28df96e66579852058b0 +README.md: 98765a809820bcd4aeba31e1dd4ef6e92eb30760 +README.zh.md: dbc499104bac6cf09f9b1c871143da8781a8f082 diff --git a/packages/goal/goal/README.md b/packages/goal/goal/README.md index fc2fd87910..98765a8098 100644 --- a/packages/goal/goal/README.md +++ b/packages/goal/goal/README.md @@ -17,7 +17,7 @@ Event-sourced same-session goal state. The service retains one current completio ## Service contract -`ctx.goals` accepts only the exact live `Agent` instance registered under its id. `get()` returns a detached `GoalView`; mutations use a `GoalRef { id, revision }` compare-and-set fence and reject stale refs. The service exposes create, edit, pause, resume, complete, block, and clear verbs through the generated [service catalog](../../../docs/cordis-catalog/services.md). Creation default resolution is internal. `disarm()` is the lifecycle-only exception: it removes process-local continuation authority without writing a revision or emitting a mutation. +`ctx.goals` accepts only the exact live `Agent` instance registered under its id. `get()` returns a detached `GoalView`; mutations use a `GoalRef { id, revision }` compare-and-set fence and reject stale refs. The service exposes create, edit, pause, resume, complete, block, and clear verbs through the generated region of [goal.md](../../../docs/subsystems/goal.md#cordis-surface). Creation default resolution is internal. `disarm()` is the lifecycle-only exception: it removes process-local continuation authority without writing a revision or emitting a mutation. At most one goal is current. Creation produces an active revision-one goal and arms it. A non-complete goal must be edited, transitioned, or cleared; a completed goal may be replaced by a globally fresh id. Edits retain phase, blocker reason, and activation. Pause, completion, blocking, and clear disarm activation. A block records a policy-owned lower-kebab-case code plus a normalized free-form explanation; provider limits, configured budgets, execution errors, and requests for human input all use this one durable phase rather than multiplying lifecycle states. Resume accepts a stopped phase or a disarmed active goal only while the configured round cap has remaining capacity; it clears any former blocker reason. An active armed goal rejects the redundant operation. diff --git a/packages/goal/goal/README.zh.md b/packages/goal/goal/README.zh.md index 311ec1d034..dbc499104b 100644 --- a/packages/goal/goal/README.zh.md +++ b/packages/goal/goal/README.zh.md @@ -17,7 +17,7 @@ ## 服务契约 -`ctx.goals` 只接受以对应 id 注册的完全相同的活跃 `Agent` 实例。`get()` 返回与内部状态脱离的 `GoalView`;变更通过 `GoalRef { id, revision }` 比较并设置屏障,并拒绝陈旧引用。服务通过生成的[服务目录](../../../docs/cordis-catalog/services.md)公开 create、edit、pause、resume、complete、block 和 clear 动词。创建默认值在内部解析。`disarm()` 是仅供生命周期使用的例外:它移除进程本地续行权限,不写入新 revision,也不发出变更。 +`ctx.goals` 只接受以对应 id 注册的完全相同的活跃 `Agent` 实例。`get()` 返回分离的 `GoalView`;变更通过 `GoalRef { id, revision }` 比较并设置限制,并拒绝陈旧引用。服务通过 [goal.md](../../../docs/subsystems/goal.md#cordis-surface) 的生成区块公开 create、edit、pause、resume、complete、block 和 clear 动词。创建默认值在内部解析。`disarm()` 是仅供生命周期使用的例外:它移除进程本地续行权限,不写入新 revision,也不发送变更事件。 最多只有一个当前目标。创建操作会生成 revision 为 1、phase 为 active 的目标并启用续行。未完成的目标必须编辑、转换或清除;已完成目标可以由拥有全局未使用过的 id 的目标替换。编辑会保留 phase、blocker reason 与 activation。暂停、完成、阻塞和清除都会停用续行。阻塞会记录策略自有的 lower-kebab-case 代码和规范化的自由文本说明;提供方限制、配置预算、执行错误与请求人工输入都使用这一种持久 phase,不会扩增生命周期状态。只有配置的 Round 上限仍有剩余容量时,resume 才接受已停止 phase 或 phase 为 active 但已停用续行的目标;它会清除原 blocker reason。phase 为 active 且已启用续行的目标会拒绝冗余操作。 diff --git a/packages/interaction/user-approval/README.i18n.yaml b/packages/interaction/user-approval/README.i18n.yaml index 36188334e7..2122c0d101 100644 --- a/packages/interaction/user-approval/README.i18n.yaml +++ b/packages/interaction/user-approval/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/user-approval/README.md -README.md: 7b87a75d1c7c43874c484bc11f8deed45cb523ce -README.zh.md: 3c02f8d673a62c3ee954da26f6341b433032a88c +README.md: 0cf5d458863194e29f8c84168a6f089baabbf3d2 +README.zh.md: a48e4ff4650fbd90744c9d7c41878b6e0487127c diff --git a/packages/interaction/user-approval/README.md b/packages/interaction/user-approval/README.md index 7b87a75d1c..0cf5d45886 100644 --- a/packages/interaction/user-approval/README.md +++ b/packages/interaction/user-approval/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -Channel-neutral one-shot approval seam. `ctx.approval.request(req)` returns `allowed-once`, `rejected`, `cancelled`, or `unavailable`; missing or failing answerers fail closed, and a grant applies only to the requested action. Exact event signatures live in the generated [Cordis catalog](../../../docs/cordis-catalog/events.md). +Channel-neutral one-shot approval seam. `ctx.approval.request(req)` returns `allowed-once`, `rejected`, `cancelled`, or `unavailable`; missing or failing answerers fail closed, and a grant applies only to the requested action. Exact event signatures live in the generated region of [approval.md](../../../docs/subsystems/approval.md#cordis-surface). Each request must belong to an open agent turn. The service appends a paired `approval/asked` and `approval/decided` audit record, while the model sees only the resulting logged tool outcome. An aborted request resolves `cancelled`; an audit append that fails before commit rejects rather than returning an unlogged decision. diff --git a/packages/interaction/user-approval/README.zh.md b/packages/interaction/user-approval/README.zh.md index 3c02f8d673..a48e4ff465 100644 --- a/packages/interaction/user-approval/README.zh.md +++ b/packages/interaction/user-approval/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -与通道无关的一次性审批 seam。`ctx.approval.request(req)` 返回 `allowed-once`、`rejected`、`cancelled` 或 `unavailable`;应答者缺失或失败时会以拒绝方式关闭,授权也只适用于所请求的操作。确切事件签名见生成的 [Cordis 目录](../../../docs/cordis-catalog/events.md)。 +与通道无关的一次性审批 seam。`ctx.approval.request(req)` 返回 `allowed-once`、`rejected`、`cancelled` 或 `unavailable`;应答者缺失或失败时会以拒绝方式关闭,授权也只适用于所请求的操作。确切事件签名见 [approval.md](../../../docs/subsystems/approval.md#cordis-surface) 的生成区块。 每个请求都必须属于一个尚未结束的 agent(智能体)轮次。服务会追加一对 `approval/asked` 与 `approval/decided` 审计记录,而模型只会看到由此产生且已写入日志的工具结果。已中止的请求会解析为 `cancelled`;如果审计记录的追加在提交前失败,Promise 会被拒绝,而不会返回一项未记录的决定。 diff --git a/packages/self-modification/tool-cordis/README.i18n.yaml b/packages/self-modification/tool-cordis/README.i18n.yaml index c25dda28e3..ef1f441711 100644 --- a/packages/self-modification/tool-cordis/README.i18n.yaml +++ b/packages/self-modification/tool-cordis/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/self-modification/tool-cordis/README.md -README.md: 9986310160c2b56126155a4c3ef84d66018d31c2 -README.zh.md: d955306e1e5d4154f58c771704782ece44a15c99 +README.md: f2a65043a1d2f74553e98caf59ed3d38b5a70b7c +README.zh.md: 66742094992d219ccfbd60b935dcd10e48cb12b8 diff --git a/packages/self-modification/tool-cordis/README.md b/packages/self-modification/tool-cordis/README.md index 9986310160..f2a65043a1 100644 --- a/packages/self-modification/tool-cordis/README.md +++ b/packages/self-modification/tool-cordis/README.md @@ -28,7 +28,7 @@ The sandbox isolates globals but is not a security boundary. Node globals are ab ## The generated API catalog -`src/api-catalog.ts` is generated from the same Typert `FaceModel` projection as [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) and freshness-gated by `pnpm run verify-cordis-api` (in `doc-sync`) — never edit it by hand. `scripts/gen-cordis-api.ts` is a compatibility entry point for that unified projection, not a second collector. `cordis_inspect` intersects the committed catalog with the live service store at call time; it has no runtime Typert dependency. Broad `api` / `events` reports render summaries and signatures only; an exact `name` opts into the retained method/event JSDoc, and unknown or non-running service targets fail loud. +`src/api-catalog.ts` is generated from the same Typert `FaceModel` projection as the [subsystem pages' generated regions](../../../docs/subsystems/core.md) and freshness-gated by `pnpm run verify-cordis-api` (in `doc-sync`) — never edit it by hand. `scripts/gen-cordis-api.ts` is a compatibility entry point for that unified projection, not a second collector. `cordis_inspect` intersects the committed catalog with the live service store at call time; it has no runtime Typert dependency. Broad `api` / `events` reports render summaries and signatures only; an exact `name` opts into the retained method/event JSDoc, and unknown or non-running service targets fail loud. ## Rendering diff --git a/packages/self-modification/tool-cordis/README.zh.md b/packages/self-modification/tool-cordis/README.zh.md index d955306e1e..6674209499 100644 --- a/packages/self-modification/tool-cordis/README.zh.md +++ b/packages/self-modification/tool-cordis/README.zh.md @@ -28,7 +28,7 @@ ## 生成的 API 目录 -`src/api-catalog.ts` 与 [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) 由同一个 Typert `FaceModel` 投影生成,并由 `pnpm run verify-cordis-api`(位于 `doc-sync` 中)实施新鲜度门禁,绝不可手工编辑。`scripts/gen-cordis-api.ts` 是该统一投影的兼容入口,而非第二套收集器。`cordis_inspect` 在调用时把已提交的目录与存活服务 store 取交集;它在运行时不依赖 Typert。宽泛的 `api`/`events` 报告只渲染摘要与签名;精确 `name` 会选择保留的方法/事件 JSDoc,未知或未运行的服务目标会高声失败。 +`src/api-catalog.ts` 与[子系统页面的生成区块](../../../docs/subsystems/core.md)由同一个 Typert `FaceModel` 投影生成,并由 `pnpm run verify-cordis-api`(位于 `doc-sync` 中)实施新鲜度门禁,绝不可手工编辑。`scripts/gen-cordis-api.ts` 是该统一投影的兼容入口,而非第二套收集器。`cordis_inspect` 在调用时把已提交的目录与存活服务 store 取交集;它在运行时不依赖 Typert。宽泛的 `api`/`events` 报告只渲染摘要与签名;精确 `name` 会选择保留的方法/事件 JSDoc,未知或未运行的服务目标会高声失败。 ## 渲染 diff --git a/packages/typert/generator/src/cordis-catalog.ts b/packages/typert/generator/src/cordis-catalog.ts index d62739acdd..1950c255cb 100644 --- a/packages/typert/generator/src/cordis-catalog.ts +++ b/packages/typert/generator/src/cordis-catalog.ts @@ -694,32 +694,61 @@ function renderRuntimeApi( lines.push(']', '') return lines.join('\n') } -/** Render the cross-link "Types:" line for a signature, or '' if none apply. */ -function typeLinks(signature: string, linkedTypePages: Readonly>): string { +/** Opening region delimiter; injected content lives between the pair and the page owns everything outside. */ +export const REGION_BEGIN = '' +/** Closing region delimiter matching {@link REGION_BEGIN}. */ +export const REGION_END = '' + +/** + * Render the cross-link "Types:" line for a signature relative to one + * subsystems page, or '' if none apply. A type whose primary page IS the + * rendering page would link as a fragmentless self-link readers already sit + * on, so it is dropped instead. + */ +function typeLinks(signature: string, onPage: string, linkedTypePages: Readonly>): string { const seen = new Set() for (const name of Object.keys(linkedTypePages)) { if (new RegExp(`\\b${name}\\b`).test(signature)) seen.add(name) } - if (seen.size === 0) return '' - const links = [...seen].sort().map(n => `[${n}](../subsystems/${linkedTypePages[n]})`) + const links = [...seen].sort() + .filter(name => linkedTypePages[name] !== onPage) + .map(name => `[${name}](${linkedTypePages[name]})`) + if (links.length === 0) return '' return `Types: ${links.join(' · ')}` } -/** Render one harness event entry. */ -function renderEvent(e: EventEntry, linkedTypePages: Readonly>): string[] { - const out = [`### \`${e.name}\` — ${e.mode}`, ''] +/** + * GitHub's heading-slug algorithm (lowercase; drop everything but letters, + * numbers, spaces, hyphens; spaces become hyphens). Region headings carry + * backticks and em-dashes, which VitePress slugifies differently, so each + * generated heading is preceded by an explicit `` carrying this slug — + * the historical flat-catalog anchor — making `#ctx--` fragments + * resolve identically on GitHub and the published site. + */ +function githubSlug(heading: string): string { + return heading.toLowerCase().replace(/[^\p{L}\p{N} -]/gu, '').replaceAll(' ', '-') +} + +/** The explicit-anchor line emitted before one generated heading. */ +function anchorFor(headingText: string): string[] { + return [``, ''] +} + +/** Render one harness event entry onto its owning page, nested under its scope heading. */ +function renderEvent(e: EventEntry, onPage: string, linkedTypePages: Readonly>): string[] { + const out = [...anchorFor(`${e.name} — ${e.mode}`), `#### \`${e.name}\` — ${e.mode}`, ''] if (e.doc) out.push(e.doc, '') out.push('```' + FENCE, e.jsDoc, e.signature, '```', '') - const links = typeLinks(e.signature, linkedTypePages) + const links = typeLinks(e.signature, onPage, linkedTypePages) 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, linkedTypePages: Readonly>): string[] { +/** Render one harness service entry onto its owning page. */ +function renderService(s: ServiceEntry, onPage: string, linkedTypePages: Readonly>): string[] { const kind = s.abstract ? ' (abstract seam)' : '' - const out = [`## \`ctx.${s.key}\` — \`${s.type}\`${kind}`, ''] + const out = [...anchorFor(`ctx.${s.key} — ${s.type}${kind}`), `### \`ctx.${s.key}\` — \`${s.type}\`${kind}`, ''] if (s.doc) out.push(s.doc, '') if (s.methods.length) { const declarations = s.methods.flatMap((method, index) => [ @@ -728,7 +757,7 @@ function renderService(s: ServiceEntry, linkedTypePages: Readonly method.signature).join('\n'), linkedTypePages) + const links = typeLinks(s.methods.map(method => method.signature).join('\n'), onPage, linkedTypePages) if (links) out.push(links, '') } out.push(`Source: [\`${s.source}\`](../../${s.source.split(':')[0]})`, '') @@ -746,36 +775,62 @@ const BANNER = [ 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 deterministically. - * @param events - validated event entries to render. - * @param policy - type links and inherited events supplied by the caller. + * Render one page's generated `cordis-surface` region: the services mapped to + * the page, then the event scopes mapped to it, markers included. Pure and + * deterministic given sorted inputs; identical bytes land in both pair sides. + * @param page - the owning `docs/subsystems/` page basename, e.g. `core.md`. + * @param services - validated services mapped to this page. + * @param events - validated events whose scopes map to this page. + * @param policy - type links supplied by the caller. + * @returns the complete marker-delimited region text. + */ +export function renderPageRegion(page: string, services: ServiceEntry[], events: EventEntry[], policy: CordisCatalogPolicy): string { + const lines: string[] = [ + REGION_BEGIN, + '', + '', + '', + '## Cordis surface', + '', + 'Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` surface lives in [cordis-api/inherited.md](../cordis-api/inherited.md).', + '', + ] + for (const s of services) lines.push(...renderService(s, page, policy.linkedTypePages)) + const scopes = [...new Set(events.map(e => e.scope))].sort() + for (const scope of scopes) { + lines.push(...anchorFor(`${scope}/* events`), `### \`${scope}/*\` events`, '') + for (const e of events.filter(x => x.scope === scope).sort((a, b) => a.name.localeCompare(b.name))) { + lines.push(...renderEvent(e, page, policy.linkedTypePages)) + } + } + while (lines.at(-1) === '') lines.pop() + lines.push(REGION_END) + return lines.join('\n') +} + +/** + * Render the inherited (pinned vendor) tier as its own generated page. + * @param policy - inherited events and services supplied by the caller. * @returns the complete generated Markdown document. */ -export function renderEvents(events: EventEntry[], policy: CordisCatalogPolicy): string { +export function renderInheritedPage(policy: CordisCatalogPolicy): string { const lines: string[] = [ ...BANNER, - '# Cordis Events Catalog', + '# Inherited Cordis Surface', '', - '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.` surface is the sibling [services catalog](services.md), and [subsystems/](../subsystems/core.md) catalogs the *data structures* these signatures move around.', + 'The framework `ctx` members and events every plugin sees beyond the harness tier — pinned vendor source ([vendoring policy](../../vendor/README.md)), summarized tersely so the harness pages stay focused on repository-owned vocabulary. Detailed Context, Fiber, Registry, and Service APIs are generated in [context.md](context.md), [fiber.md](fiber.md), [registry.md](registry.md), and [service.md](service.md); the event-dispatch methods in [events.md](events.md).', '', 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`).', + '## Inherited `ctx` members (cordis core + loader/hmr/timer)', '', ] - 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, policy.linkedTypePages)) - } + for (const s of policy.inheritedServices) { + lines.push(`- \`${s.name}\` — ${s.summary} ([\`${s.source}\`](../../${s.source.split(':')[0]}))`) } 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.', + '## Inherited events (cordis core + loader/hmr/timer)', '', ) for (const e of policy.inheritedEvents) { @@ -784,35 +839,3 @@ export function renderEvents(events: EventEntry[], policy: CordisCatalogPolicy): lines.push('') return lines.join('\n') } - -/** - * Render the services catalog deterministically. - * @param services - validated service entries to render. - * @param policy - type links and inherited services supplied by the caller. - * @returns the complete generated Markdown document. - */ -export function renderServices(services: ServiceEntry[], policy: CordisCatalogPolicy): string { - const lines: string[] = [ - ...BANNER, - '# Cordis Services Catalog', - '', - 'Every `ctx.` 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, policy.linkedTypePages)) - 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 policy.inheritedServices) { - lines.push(`- \`${s.name}\` — ${s.summary} ([\`${s.source}\`](../../${s.source.split(':')[0]}))`) - } - lines.push('') - return lines.join('\n') -} diff --git a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts index 1b6589b6cf..bde52505dc 100644 --- a/packages/typert/generator/tests/cordis-catalog-contract.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog-contract.spec.ts @@ -10,8 +10,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { collectEvents as collectEventsWithPolicy, collectServices as collectServicesWithPolicy, - renderEvents as renderEventsWithPolicy, - renderServices as renderServicesWithPolicy, + renderPageRegion, } from '../src/cordis-catalog.ts' import type { CordisCatalogPolicy, @@ -35,12 +34,12 @@ function collectServices(root: string): ServiceEntry[] { return collectServicesWithPolicy(root, TEST_POLICY) } -function renderEvents(events: EventEntry[]): string { - return renderEventsWithPolicy(events, TEST_POLICY) +function renderEvents(events: EventEntry[], onPage = 'bash.md'): string { + return renderPageRegion(onPage, [], events, TEST_POLICY) } -function renderServices(services: ServiceEntry[]): string { - return renderServicesWithPolicy(services, TEST_POLICY) +function renderServices(services: ServiceEntry[], onPage = 'bash.md'): string { + return renderPageRegion(onPage, services, [], TEST_POLICY) } const TYPE_FIXTURES = [ @@ -155,7 +154,8 @@ describe.skip('gen-cordis-catalog collectEvents', { timeout: 60_000 }, () => { ' /**\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\'(value: Readonly, preset: PresetSpec, signal: AbortSignal): Promise', )) expect(events).toHaveLength(1) - expect(renderEvents(events)).toContain('Types: [SessionEvent](../subsystems/core.md)') + expect(renderEvents(events)).toContain('Types: [SessionEvent](core.md)') + expect(renderEvents(events, 'core.md')).not.toContain('Types: [SessionEvent]') expect(renderEvents(events)).not.toContain('[PresetSpec]') }) diff --git a/packages/typert/generator/tests/cordis-catalog.spec.ts b/packages/typert/generator/tests/cordis-catalog.spec.ts index 644bb28de2..9474c15145 100644 --- a/packages/typert/generator/tests/cordis-catalog.spec.ts +++ b/packages/typert/generator/tests/cordis-catalog.spec.ts @@ -3,10 +3,10 @@ import { join, resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { projectCordisCatalog, - renderEvents, - renderServices, + renderInheritedPage, + renderPageRegion, } from '../src/cordis-catalog.ts' -import { CORDIS_CATALOG_POLICY } from '../../../../scripts/gen-cordis-catalog.ts' +import { CORDIS_CATALOG_POLICY, EVENT_SCOPE_PAGE, REGION_BEGIN, REGION_END, SERVICE_PAGE } from '../../../../scripts/gen-cordis-catalog.ts' const workspaceRoot = resolve(import.meta.dirname, '../../../..') @@ -15,8 +15,22 @@ describe('Typert-backed Cordis catalog', () => { const { projector, model } = projectCordisCatalog(workspaceRoot, CORDIS_CATALOG_POLICY) const expected = (path: string): string => readFileSync(join(workspaceRoot, path), 'utf8') - expect(renderEvents([...model.events], CORDIS_CATALOG_POLICY)).toBe(expected('docs/cordis-catalog/events.md')) - expect(renderServices([...model.services], CORDIS_CATALOG_POLICY)).toBe(expected('docs/cordis-catalog/services.md')) + expect(renderInheritedPage(CORDIS_CATALOG_POLICY)).toBe(expected('docs/cordis-api/inherited.md')) + for (const page of [...new Set([...Object.values(SERVICE_PAGE), ...Object.values(EVENT_SCOPE_PAGE)])].sort()) { + const region = renderPageRegion( + page, + [...model.services].filter(s => SERVICE_PAGE[s.key] === page), + [...model.events].filter(e => EVENT_SCOPE_PAGE[e.scope] === page), + CORDIS_CATALOG_POLICY, + ) + for (const side of [page, page.replace(/\.md$/, '.zh.md')]) { + const committed = expected(`docs/subsystems/${side}`) + const begin = committed.indexOf(REGION_BEGIN) + const end = committed.indexOf(REGION_END) + expect(begin, `docs/subsystems/${side} carries the region`).toBeGreaterThanOrEqual(0) + expect(committed.slice(begin, end + REGION_END.length)).toBe(region) + } + } expect(projector.renderRuntimeApi(model)).toBe( expected('packages/self-modification/tool-cordis/src/api-catalog.ts'), ) diff --git a/scripts/cordis-core-api.spec.ts b/scripts/cordis-core-api.spec.ts index d35899553c..8aef4041a1 100644 --- a/scripts/cordis-core-api.spec.ts +++ b/scripts/cordis-core-api.spec.ts @@ -21,13 +21,13 @@ describe('Cordis core API generation', () => { it('renders the five detailed pages from pinned vendor declarations', () => { const pages = renderCordisCoreApiPages() expect([...pages.keys()]).toEqual(CORDIS_CORE_API_PAGES.map(page => page.out)) - expect(pages.get('docs/cordis-catalog/core/context.md')).toContain('### ctx.extend(meta?)') - expect(pages.get('docs/cordis-catalog/core/events.md')).toContain('## DispatchMode') - expect(pages.get('docs/cordis-catalog/core/fiber.md')).toContain('## EffectMeta') - expect(pages.get('docs/cordis-catalog/core/registry.md')).toContain('## Plugin') - expect(pages.get('docs/cordis-catalog/core/service.md')).toContain('### Service.resolveConfig') + expect(pages.get('docs/cordis-api/context.md')).toContain('### ctx.extend(meta?)') + expect(pages.get('docs/cordis-api/events.md')).toContain('## DispatchMode') + expect(pages.get('docs/cordis-api/fiber.md')).toContain('## EffectMeta') + expect(pages.get('docs/cordis-api/registry.md')).toContain('## Plugin') + expect(pages.get('docs/cordis-api/service.md')).toContain('### Service.resolveConfig') - const fiber = pages.get('docs/cordis-catalog/core/fiber.md') ?? '' + const fiber = pages.get('docs/cordis-api/fiber.md') ?? '' expect(fiber).toContain('```\n\nRegister a cleanup-aware effect on this fiber.') expect(fiber).toContain('- `execute` — the effect body; see `Effect` for accepted shapes.') expect(fiber).toContain('**Returns** a disposer that tears the effect down and settles once done.') @@ -39,7 +39,7 @@ describe('Cordis core API generation', () => { mkdirSync(join(root, 'vendor/cordis/src'), { recursive: true }) writeFileSync(join(root, 'vendor/cordis/src/service.ts'), 'export class Service {\n run(): string { return "ok" }\n}\n') const page: CordisCoreApiPage = { - out: 'docs/cordis-catalog/core/service.md', + out: 'docs/cordis-api/service.md', title: 'Service', intro: 'Service API.', sections: [{ kind: 'class', file: 'vendor/cordis/src/service.ts', symbol: 'Service' }], diff --git a/scripts/cordis-core-api.ts b/scripts/cordis-core-api.ts index a2400fdb54..a32c1ce8fa 100644 --- a/scripts/cordis-core-api.ts +++ b/scripts/cordis-core-api.ts @@ -26,7 +26,7 @@ export interface CordisCoreApiPage { /** Explicit editorial grouping for the pinned Cordis core surface. */ export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [ { - out: 'docs/cordis-catalog/core/context.md', + out: 'docs/cordis-api/context.md', title: 'Context', intro: 'The context is the core Cordis object: every service, event, and lifecycle API is reached through `ctx`. Event methods are documented on [Events](events.md), effects and the current fiber on [Fiber](fiber.md), and plugin loading on [Registry](registry.md).', sections: [ @@ -35,9 +35,9 @@ export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [ ], }, { - out: 'docs/cordis-catalog/core/events.md', + out: 'docs/cordis-api/events.md', title: 'Events', - intro: 'The event-dispatch API mixed into every context. Harness event declarations and their dispatch modes are generated separately in the [Cordis events catalog](../events.md).', + intro: 'The event-dispatch API mixed into every context. Harness event declarations and their dispatch modes are generated into each owning [subsystem page](../subsystems/core.md).', sections: [ { kind: 'context-merge', file: 'vendor/cordis/src/events.ts' }, { kind: 'decl', file: 'vendor/cordis/src/events.ts', symbol: 'EventOptions' }, @@ -45,7 +45,7 @@ export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [ ], }, { - out: 'docs/cordis-catalog/core/fiber.md', + out: 'docs/cordis-api/fiber.md', title: 'Fiber', intro: 'A fiber is one loaded plugin instance: its lifecycle state, validated config, and registered effects. `ctx.fiber` is the current fiber, and `ctx.effect()` delegates to it.', sections: [ @@ -59,7 +59,7 @@ export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [ ], }, { - out: 'docs/cordis-catalog/core/registry.md', + out: 'docs/cordis-api/registry.md', title: 'Registry', intro: 'Plugin loading and dependency injection.', sections: [ @@ -69,7 +69,7 @@ export const CORDIS_CORE_API_PAGES: CordisCoreApiPage[] = [ ], }, { - out: 'docs/cordis-catalog/core/service.md', + out: 'docs/cordis-api/service.md', title: 'Service', intro: 'The base class for context services. A subclass loaded as a plugin registers itself as `ctx.`.', sections: [ @@ -357,7 +357,7 @@ function declarationPaste(ctx: RenderContext, rel: string, symbol: string): { do function sourceLink(source: string): string { const [file, line] = source.split(':') - return `[Source](../../../${file}${line === undefined ? '' : `#L${line}`})` + return `[Source](../../${file}${line === undefined ? '' : `#L${line}`})` } function unlink(text: string): string { diff --git a/scripts/cordis-walk.ts b/scripts/cordis-walk.ts index e414b572d3..0111d1d042 100644 --- a/scripts/cordis-walk.ts +++ b/scripts/cordis-walk.ts @@ -1,7 +1,39 @@ -/** Locate the Cordis module merge used by the vendored core API projector. */ +/** + * AST helpers shared by the Cordis generators: locate the Cordis module merge + * in a source file and enumerate the `interface Context` keys it declares. + * The vendored core API projector consumes the merge body; the per-subsystem + * region generator's exhaustiveness backstop consumes the key scan. + */ +import { globSync, readFileSync } from 'node:fs' +import { resolve, sep } from 'node:path' import ts from 'typescript' +/** + * Parse every file matching `pattern` (repo-relative, sorted, `/`-normalized) + * that textually mentions `interface Context`, yielding each file's cordis + * module-merge body. Files without a merge are skipped. + * @param scanRoot - Repository root the pattern is resolved against. + * @param pattern - Glob selecting the TypeScript files to scan. + * @returns One entry per file with a cordis module merge, in path order. + */ +export function contextMergeFiles( + scanRoot: string, + pattern: string, +): { rel: string; sf: ts.SourceFile; text: string; body: ts.ModuleBlock }[] { + const out: { rel: string; sf: ts.SourceFile; text: string; body: ts.ModuleBlock }[] = [] + for (const rel of globSync(pattern, { 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 + out.push({ rel, sf, text, body }) + } + return out +} + /** The body of the cordis module merge in `sf`: `declare module 'cordis'` * (harness packages) or `declare module './context.ts'` (vendor core), or * null when the file has neither. */ @@ -13,3 +45,22 @@ export function cordisModuleBody(sf: ts.SourceFile): ts.ModuleBlock | null { } return null } + +/** + * Every `key: Type` property a `declare module 'cordis'` Context merge + * declares in one module body. + * @param body - The cordis module augmentation block. + * @param sf - Owning source file (for text extraction). + * @returns key → declared type-name text, in declaration order. + */ +export function contextKeyMap(body: ts.ModuleBlock, sf: ts.SourceFile): Map { + const keyToType = new Map() + for (const stmt of body.statements) { + if (!ts.isInterfaceDeclaration(stmt) || stmt.name.text !== 'Context') continue + for (const member of stmt.members) { + if (!ts.isPropertySignature(member) || !member.type) continue + keyToType.set(member.name.getText(sf), member.type.getText(sf)) + } + } + return keyToType +} diff --git a/scripts/gen-config-catalog.ts b/scripts/gen-config-catalog.ts index ebceec916b..a79ffdd47f 100644 --- a/scripts/gen-config-catalog.ts +++ b/scripts/gen-config-catalog.ts @@ -819,7 +819,7 @@ export function render(entries: CatalogEntry[]): string { '', '# Plugin Config Catalog', '', - 'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the cordis [events](cordis-catalog/events.md) + [services](cordis-catalog/services.md) catalogs, the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference.', + 'Every `config:` block a `cordis.yml` entry can set: for each loadable harness package, the verbatim config declaration (JSDoc included) its `apply` function or service constructor receives, with every referenced type pasted alongside (package-local types) or linked (everything else). The paste is the plugin\'s full declared config type — a field the runtime schema deliberately excludes is a runtime-only seam (its own JSDoc says so) and is not settable from `cordis.yml`. This is the **deployment**-axis reference — the wiring a plugin author works against is the generated `cordis-surface` region on each [subsystem page](subsystems/core.md), the model-facing tool schemas are the [tool catalog](tool-catalog.md), and [subsystems/](subsystems/core.md) documents the types these declarations reference.', '', 'This file is GENERATED from source (`scripts/gen-config-catalog.ts`) and verified fresh by `pnpm run verify-config-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks use a `ts config-catalog` fence (skipped by doc-typecheck, since a lone declaration referencing imports is not standalone-compilable). The generator also cross-checks the runtime schemastery schema against the pasted declaration — every schema-validated key, nested keys included, must be locatable on the declared config type — so the paste cannot hide a loader-accepted field.', '', diff --git a/scripts/gen-cordis-catalog-record.spec.ts b/scripts/gen-cordis-catalog-record.spec.ts new file mode 100644 index 0000000000..73720e8be8 --- /dev/null +++ b/scripts/gen-cordis-catalog-record.spec.ts @@ -0,0 +1,144 @@ +/** + * Negative-path coverage for the guarded pair auto-record + * (`maybeRecordPair`): the safety property is that regeneration re-records a + * pair's `.i18n.yaml` ONLY for a region-confined write over a well-formed, + * previously-consistent record — every other state is left for the pairing + * gate to report. + */ + +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { afterEach, describe, expect, it } from 'vitest' +import { maybeRecordPair, REGION_BEGIN, REGION_END, spliceRegion } from './gen-cordis-catalog.ts' +import { blobHash, renderPairMeta } from './translation-pairing.ts' + +const PAGE = 'docs/subsystems/fix.md' +const ZH = 'docs/subsystems/fix.zh.md' +const META = 'docs/subsystems/fix.i18n.yaml' + +function page(prose: string, region: string): string { + return `# Fix\n\n${prose}\n\n${REGION_BEGIN}\n${region}\n${REGION_END}\n` +} + +const roots: string[] = [] +afterEach(() => { + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) +}) + +/** Lay out a pair on disk and return { root, before } for a regeneration that already wrote `current`. */ +function setup(options: { + beforeEn: string + beforeZh: string + currentEn: string + currentZh: string + meta?: string | null + omitZhSnapshot?: boolean +}): { root: string; before: Map } { + const root = mkdtempSync(join(tmpdir(), 'record-guard-')) + roots.push(root) + mkdirSync(join(root, 'docs/subsystems'), { recursive: true }) + writeFileSync(join(root, PAGE), options.currentEn) + writeFileSync(join(root, ZH), options.currentZh) + const meta = options.meta === undefined + ? renderPairMeta(PAGE, blobHash(Buffer.from(options.beforeEn)), ZH, blobHash(Buffer.from(options.beforeZh))) + : options.meta + if (meta !== null) writeFileSync(join(root, META), meta) + const before = new Map([[PAGE, Buffer.from(options.beforeEn)]]) + if (!options.omitZhSnapshot) before.set(ZH, Buffer.from(options.beforeZh)) + return { root, before } +} + +describe('maybeRecordPair', () => { + const beforeEn = page('prose.', 'old region') + const beforeZh = page('散文。', 'old region') + const currentEn = page('prose.', 'new region') + const currentZh = page('散文。', 'new region') + + it('re-records a region-confined write over a consistent record', () => { + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh }) + expect(maybeRecordPair(PAGE, before, root)).toBe(true) + expect(readFileSync(join(root, META), 'utf8')) + .toBe(renderPairMeta(PAGE, blobHash(Buffer.from(currentEn)), ZH, blobHash(Buffer.from(currentZh)))) + }) + + it('refuses when the pair was already out of sync before the run', () => { + const stale = renderPairMeta(PAGE, blobHash(Buffer.from('drifted long ago\n')), ZH, blobHash(Buffer.from(beforeZh))) + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, meta: stale }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + expect(readFileSync(join(root, META), 'utf8')).toBe(stale) + }) + + it('refuses a malformed record even when its hashes are current', () => { + // A renamed key with preserved hashes must stay the pairing gate's error, + // never become valid through regeneration. + const renamedKeys = [ + '# comment', + `fixXmd: ${blobHash(Buffer.from(beforeEn))}`, + `fix.zh.md: ${blobHash(Buffer.from(beforeZh))}`, + '', + ].join('\n') + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, meta: renamedKeys }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + expect(readFileSync(join(root, META), 'utf8')).toBe(renamedKeys) + }) + + it('refuses a record with extra entries', () => { + const extra = renderPairMeta(PAGE, blobHash(Buffer.from(beforeEn)), ZH, blobHash(Buffer.from(beforeZh))) + + `other.md: ${blobHash(Buffer.from(beforeEn))}\n` + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, meta: extra }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + }) + + it('refuses a record with a duplicated expected key', () => { + // Map#set would collapse the duplicate back to size 2; the parser must + // reject the repeat instead of letting the guard accept the record. + const duplicated = [ + `fix.md: ${blobHash(Buffer.from(beforeEn))}`, + `fix.md: ${blobHash(Buffer.from(beforeEn))}`, + `fix.zh.md: ${blobHash(Buffer.from(beforeZh))}`, + '', + ].join('\n') + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, meta: duplicated }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + expect(readFileSync(join(root, META), 'utf8')).toBe(duplicated) + }) + + it('refuses when prose drifted alongside the region write', () => { + const proseDrift = page('prose, edited by a human.', 'new region') + const { root, before } = setup({ beforeEn, beforeZh, currentEn: proseDrift, currentZh }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + }) + + it('refuses a brand-new pair with no record', () => { + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, meta: null }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + }) + + it('refuses when a side has no pre-write snapshot', () => { + const { root, before } = setup({ beforeEn, beforeZh, currentEn, currentZh, omitZhSnapshot: true }) + expect(maybeRecordPair(PAGE, before, root)).toBe(false) + }) +}) + +describe('spliceRegion', () => { + it('replaces exactly the cordis-surface region', () => { + const doc = `# T\n\nprose\n\n${REGION_BEGIN}\nold\n${REGION_END}\ntail\n` + expect(spliceRegion(doc, `${REGION_BEGIN}\nnew\n${REGION_END}`)) + .toBe(`# T\n\nprose\n\n${REGION_BEGIN}\nnew\n${REGION_END}\ntail\n`) + }) + + it('fails loud on a page carrying only some other generator\'s region', () => { + // Another generator's markers satisfy the generic region grammar but must + // never be overwritten by THIS generator's splice. + const foreign = '# T\n\n\ntheirs\n\n' + expect(() => spliceRegion(foreign, `${REGION_BEGIN}\nnew\n${REGION_END}`)) + .toThrow('expected exactly 1 cordis-surface region, found 0 BEGIN/0 END') + }) + + it('fails loud on duplicate cordis-surface markers', () => { + const doubled = `${REGION_BEGIN}\na\n${REGION_END}\n${REGION_BEGIN}\nb\n${REGION_END}\n` + expect(() => spliceRegion(doubled, `${REGION_BEGIN}\nnew\n${REGION_END}`)) + .toThrow('found 2 BEGIN/2 END') + }) +}) diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 3cc277152e..120904d4b5 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -1,24 +1,156 @@ /** - * Generate committed Cordis artifacts from the Typert catalog projector and - * the independent vendored-core projector. + * Generate the per-subsystem Cordis service/event reference regions from the + * Typert catalog projection. Every harness `ctx.` service and event scope + * maps to exactly one `docs/subsystems/` page through the curated tables below; + * the generator injects each page's surface between its GENERATED markers — + * byte-identically into both language sides of the pair — and re-records a + * pair's `.i18n.yaml` only when nothing outside the region changed. The + * projection enforces event modes, JSDoc parameter/return completeness, and + * signature type-link coverage; the inherited (vendor) tier renders to + * `docs/cordis-api/inherited.md`. `--check` verifies every generated artifact. */ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { projectCordisCatalog, - renderEvents, - renderServices, + renderInheritedPage, + renderPageRegion, + REGION_BEGIN, + REGION_END, } from '@deepseek-ai/dsh-typert-generator' import type { CordisCatalogPolicy } from '@deepseek-ai/dsh-typert-generator' import { renderCordisCoreApiPages } from './cordis-core-api.ts' +import { contextKeyMap, contextMergeFiles } from './cordis-walk.ts' +import { + blobHash, + parsePairMeta, + partitionGeneratedRegions, + renderPairMeta, +} from './translation-pairing.ts' const root = resolve(import.meta.dirname, '..') -const OUT_EVENTS = 'docs/cordis-catalog/events.md' -const OUT_SERVICES = 'docs/cordis-catalog/services.md' +const SUBSYSTEMS_DIR = 'docs/subsystems' +const OUT_INHERITED = 'docs/cordis-api/inherited.md' const OUT_RUNTIME_API = 'packages/self-modification/tool-cordis/src/api-catalog.ts' -/** One primary subsystems page per project type used by a generated signature. */ +export { REGION_BEGIN, REGION_END } + +/** + * The owning subsystems page for every harness `ctx.` service the + * projection discovers. Fail-closed both ways: a discovered key absent here + * and an entry whose key the projection no longer discovers are both hard + * errors, so the partition can never silently drift from the service surface. + */ +export const SERVICE_PAGE: Record = { + agentLoop: 'core.md', + agents: 'core.md', + approval: 'approval.md', + bash: 'bash.md', + bashEnv: 'bash.md', + clientModuleHost: 'client-modules.md', + codeRuntime: 'code-runtime.md', + commands: 'commands.md', + compact: 'compaction.md', + credentials: 'credentials.md', + directoryPicker: 'workspace.md', + e2b: 'subprocess.md', + fs: 'filesystem.md', + goals: 'goal.md', + httpServer: 'http-server.md', + invariants: 'invariants.md', + llm: 'llm-streaming.md', + permission: 'permission.md', + planMode: 'plan.md', + pty: 'pty.md', + sandbox: 'sandbox.md', + sandboxPolicy: 'sandbox.md', + sessionPersistence: 'persistence.md', + sessionQuery: 'session-query.md', + sessionReferences: 'session-reference.md', + sessionProjectionCache: 'session-projection.md', + sessionProjections: 'session-projection.md', + sessions: 'session.md', + settings: 'settings.md', + sessionTitle: 'session-title.md', + skills: 'skills.md', + spillStore: 'spill.md', + storage: 'storage.md', + storageDomain: 'storage.md', + subagents: 'subagent.md', + subprocess: 'subprocess.md', + systemPrompt: 'system-prompt.md', + tasks: 'tasks.md', + telemetry: 'telemetry.md', + tokenMeter: 'token-meter.md', + toolResultPrune: 'compaction.md', + tools: 'tools.md', + typert: 'typert.md', + typertGateway: 'typert.md', + userInteraction: 'user-interaction.md', + web: 'web.md', + workflows: 'workflow.md', + workspace: 'workspace.md', +} + +/** + * Context keys declared in `interface Context` merges that the rendering + * projection cannot see, each with the reason and its documentation owner. + * The scan that enforces this list reads EVERY `declare module 'cordis'` + * Context merge under `packages/x/x/src/*.ts` — not only root `index.ts` + * files with a same-named service class — so a new service can never silently + * join this blind spot: it either enters {@link SERVICE_PAGE} or names itself + * here. + * TODO(cordis-catalog-interface-services): the interface-typed and + * non-index-declared entries would all render once the projection resolves a + * Context key through its declaring file's imports to the class declaration. + */ +export const SERVICE_WALK_EXEMPTIONS: Record = { + agent: 'not a service: the DX accessor field on Agent.ctx (root accessor defaulting to undefined) — docs/subsystems/core.md owns the Agent handle', + configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns the launcher contract', + launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns the launcher contract', + dshHomePath: 'not a service: boot-provided root accessor function (typeof dshHomePath | undefined) for Loader !!js config expressions — packages/boot/app-boot/README.md owns the boot contract', + headlessIo: 'not a service: launcher-provided root accessor value (HeadlessIo | undefined) for the headless bundle runner — packages/bundle/headless/README.md owns the launcher contract', + launcherEnvironment: 'not a service: launcher-provided root accessor value (EnvironmentSnapshot | undefined) — packages/util/environment/README.md owns the launcher contract', + lsp: 'interface-typed (LspService); implementing class Lsp is not the declared type name — packages/lsp/lsp/README.md owns the surface', + apiProxy: 'interface-typed (ApiProxy) with the class in api-proxy.ts, not index.ts — packages/host/apiproxy/README.md owns the surface', + appShell: 'client-side interface-typed browser service — packages/client/web/README.md owns the surface', + connection: 'client-side interface-typed browser service — packages/client/connection/README.md owns the surface', +} + +/** + * The owning subsystems page for every harness event scope (the segment + * before the first `/`). Fail-closed exactly like {@link SERVICE_PAGE}. + * `slash` lives with the human-command surface: the client slash-input + * protocol parses toward command invocation and `dsh-ui-slash` owns the + * declarations, but commands.md owns the cross-package command story. + */ +export const EVENT_SCOPE_PAGE: Record = { + 'agent': 'core.md', + 'agent-loop': 'core.md', + 'approval': 'approval.md', + 'commands': 'commands.md', + 'credentials': 'credentials.md', + 'domain': 'storage.md', + 'fs': 'filesystem.md', + 'goal': 'goal.md', + 'llm': 'llm-streaming.md', + 'session': 'session.md', + 'settings': 'settings.md', + 'skills': 'skills.md', + 'subagent': 'subagent.md', + 'system-prompt': 'system-prompt.md', + 'telemetry': 'telemetry.md', + 'tools': 'tools.md', + 'workflow': 'workflow.md', +} + +/** + * 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: Readonly> = { Agent: 'core.md', AgentCancelCause: 'core.md', @@ -94,12 +226,12 @@ export const LINK_MAP: Readonly> = { FsWriteIntent: 'filesystem.md', FsWriteOutcome: 'filesystem.md', CreateGoalRequest: 'goal.md', - CreateGoalResult: 'goal.md', EditGoalRequest: 'goal.md', GoalBlockReason: 'goal.md', GoalChanged: 'goal.md', GoalRef: 'goal.md', GoalView: 'goal.md', + CreateGoalResult: 'goal.md', CommandDefinition: 'commands.md', CommandDescriptor: 'commands.md', CommandResult: 'commands.md', @@ -248,6 +380,18 @@ export const LINK_MAP: Readonly> = { TelemetryRecord: 'telemetry.md', WorkflowRunInfo: 'workflow.md', WorkflowStartRequest: 'workflow.md', + ProjectionDefinition: 'session-projection.md', + SessionProjectionMap: 'session-projection.md', + ProjectionChangeListener: 'session-projection.md', + ProjectionSnapshot: 'session-projection.md', + ProjectionCheckpoint: 'session-projection.md', + DirectoryPickerCapability: 'workspace.md', + TypertContribution: 'invariants.md', + TypertFace: 'invariants.md', + TypertPackageFilter: 'invariants.md', + TypertPackageRecord: 'invariants.md', + TypertSchemaFilter: 'invariants.md', + TypertSchemaRecord: 'invariants.md', } /** TypeScript lib and pinned framework types with no repository-owned data page. */ @@ -277,31 +421,19 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { 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', 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', 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', - SessionProjectionMap: 'merge-extensible projection key map is owned by packages/session/session-projection/src/types.ts', - ProjectionChangeListener: 'change-feed listener contract is owned by packages/session/session-projection/src/index.ts', - ProjectionSnapshot: 'watermark snapshot shape is owned by packages/session/session-projection/src/index.ts', - ProjectionCheckpoint: 'persisted checkpoint row map is owned by packages/session/session-projection/src/index.ts', CommandExecution: 'executor return contract is owned by packages/interaction/commands/src/index.ts', - TypertContribution: 'registry contribution contract is owned by packages/typert/registry/README.md', - TypertFace: 'registry face identity is owned by packages/typert/registry/README.md', - TypertPackageFilter: 'registry package query filter is owned by packages/typert/registry/README.md', - TypertPackageRecord: 'registry package record is owned by packages/typert/registry/README.md', - TypertSchemaFilter: 'registry schema query filter is owned by packages/typert/registry/README.md', - TypertSchemaRecord: 'registry schema record is owned by packages/typert/registry/README.md', - TypeRTDisposer: 'TypeRT lifecycle contract is owned by packages/typert/type-meta/README.md', 'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API', 'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API', + TypeRTDisposer: 'TypeRT lifecycle contract is owned by packages/typert/type-meta/README.md', + InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md', LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts', ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts', Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts', WebUpgradeRoute: 'upgrade route registration contract is owned by packages/host/webserver/src/index.ts', InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md', - InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md', KnobState: 'projection unit state shape is owned by packages/interaction/permission/README.md', PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts', PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', @@ -351,15 +483,171 @@ export const CORDIS_CATALOG_POLICY: CordisCatalogPolicy = { ], } -/** CLI entry: default writes every artifact; `--check` reports stale files. + +/** + * Splice a page's generated cordis-surface region into its Markdown content. + * The page must contain exactly one cordis-surface region (the markers are + * part of the hand-owned page skeleton once, then owned by the generator); + * zero or several is a partition error the caller reports with the page path. + * The match is on THIS generator's exact markers, not the generic region + * grammar, so a page carrying only some other generator's region fails loud + * instead of having that region overwritten. + * @param content - the page's current full Markdown text. + * @param region - the freshly rendered marker-delimited region. + * @returns the page text with the region replaced. + */ +export function spliceRegion(content: string, region: string): string { + const lines = content.split('\n') + const begins = lines.flatMap((line, index) => (line === REGION_BEGIN ? [index] : [])) + const ends = lines.flatMap((line, index) => (line === REGION_END ? [index] : [])) + if (begins.length !== 1 || ends.length !== 1) { + throw new Error(`expected exactly 1 cordis-surface region, found ${begins.length} BEGIN/${ends.length} END; add the BEGIN/END cordis-surface markers once`) + } + const begin = begins[0] ?? -1 + const end = ends[0] ?? -1 + if (end < begin) throw new Error('cordis-surface END marker precedes its BEGIN') + return [...lines.slice(0, begin), ...region.split('\n'), ...lines.slice(end + 1)].join('\n') +} + +/** + * Compute every generated artifact: the inherited-tier page, the model-facing + * runtime API module, plus, per mapped subsystems page, the pair's two updated + * documents with the injected region. Fail-loud partition checks live here: an + * unmapped service/event scope, a mapping whose page file does not exist, a + * curated entry whose key/scope the projection no longer discovers, and a + * mapped page missing its markers are all aggregated errors. + * @returns `[repo-relative path, exact content]` for every generated artifact. + */ +export function computeOutputs(): [string, string][] { + const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY) + const services = [...model.services] + const events = [...model.events] + const problems: string[] = [] + + const discoveredKeys = new Set(services.map(s => s.key)) + const discoveredScopes = new Set(events.map(e => e.scope)) + for (const s of services) { + if (!Object.hasOwn(SERVICE_PAGE, s.key)) problems.push(`service ctx.${s.key} (${s.source}) has no SERVICE_PAGE entry; every service maps to exactly one subsystems page.`) + } + for (const scope of discoveredScopes) { + if (!Object.hasOwn(EVENT_SCOPE_PAGE, scope)) problems.push(`event scope '${scope}/*' has no EVENT_SCOPE_PAGE entry; every event scope maps to exactly one subsystems page.`) + } + for (const key of Object.keys(SERVICE_PAGE)) { + if (!discoveredKeys.has(key)) problems.push(`SERVICE_PAGE maps 'ctx.${key}' but the projection discovers no such service; remove the stale entry.`) + } + for (const scope of Object.keys(EVENT_SCOPE_PAGE)) { + if (!discoveredScopes.has(scope)) problems.push(`EVENT_SCOPE_PAGE maps '${scope}/*' but the projection discovers no such scope; remove the stale entry.`) + } + // The rendering projection only sees a Context key it can resolve to a + // documented service class. This independent scan reads EVERY Context merge + // so a key the projection cannot render must either be rendered (mapped) or + // carry a named SERVICE_WALK_EXEMPTIONS reason — never vanish silently. + const declaredKeys = new Map() + for (const { rel, sf, body } of contextMergeFiles(root, 'packages/*/*/src/*.ts')) { + for (const key of contextKeyMap(body, sf).keys()) { + if (!declaredKeys.has(key)) declaredKeys.set(key, rel) + } + } + for (const [key, rel] of declaredKeys) { + const rendered = discoveredKeys.has(key) + const exempt = Object.hasOwn(SERVICE_WALK_EXEMPTIONS, key) + if (!rendered && !exempt) { + problems.push(`ctx.${key} (${rel}) is declared in a Context merge but invisible to the rendering projection; map it in SERVICE_PAGE (after making it renderable) or name it in SERVICE_WALK_EXEMPTIONS with its documentation owner.`) + } + if (rendered && exempt) problems.push(`ctx.${key} is rendered by the projection but still listed in SERVICE_WALK_EXEMPTIONS; remove the stale exemption.`) + } + for (const key of Object.keys(SERVICE_WALK_EXEMPTIONS)) { + if (!declaredKeys.has(key)) problems.push(`SERVICE_WALK_EXEMPTIONS names 'ctx.${key}' but no Context merge declares it; remove the stale exemption.`) + } + if (problems.length > 0) throw new Error(`gen-cordis-catalog: ${problems.length} partition violation(s):\n${problems.map(p => ` ${p}`).join('\n')}`) + + const pages = [...new Set([...Object.values(SERVICE_PAGE), ...Object.values(EVENT_SCOPE_PAGE)])].sort() + const outputs: [string, string][] = [ + [OUT_INHERITED, renderInheritedPage(CORDIS_CATALOG_POLICY)], + [OUT_RUNTIME_API, projector.renderRuntimeApi(model)], + ] + for (const page of pages) { + const region = renderPageRegion( + page, + services.filter(s => SERVICE_PAGE[s.key] === page), + events.filter(e => EVENT_SCOPE_PAGE[e.scope] === page), + CORDIS_CATALOG_POLICY, + ) + for (const side of [page, page.replace(/\.md$/, '.zh.md')]) { + const rel = `${SUBSYSTEMS_DIR}/${side}` + let current: string + try { + current = readFileSync(resolve(root, rel), 'utf8') + } catch { + // Both pair sides must exist before a region can be injected; the + // pairing gate owns pair completeness, this generator names the miss. + problems.push(`${rel}: mapped subsystems page does not exist.`) + continue + } + try { + outputs.push([rel, spliceRegion(current, region)]) + } catch (error) { + problems.push(`${rel}: ${error instanceof Error ? error.message : String(error)}`) + } + } + } + if (problems.length > 0) throw new Error(`gen-cordis-catalog: ${problems.length} page violation(s):\n${problems.map(p => ` ${p}`).join('\n')}`) + return outputs +} + +/** + * Re-record a pair's `.i18n.yaml` after a region write ONLY when the write is + * region-confined: both sides' region-stripped content must be byte-equal to + * the region-stripped previous content whose hashes the record holds. The + * caller supplies the previous bytes (read before writing); human-content + * drift leaves the record untouched so the pairing gate still demands the + * normal translation flow. + * @param pageRel - repo-relative English page path (`docs/subsystems/x.md`). + * @param before - pre-write bytes per repo-relative path. + * @param scanRoot - repository root override for tests. + * @returns true when the record was refreshed. + */ +export function maybeRecordPair(pageRel: string, before: Map, scanRoot: string = root): boolean { + const zhRel = pageRel.replace(/\.md$/, '.zh.md') + const metaRel = pageRel.replace(/\.md$/, '.i18n.yaml') + const metaAbs = resolve(scanRoot, metaRel) + let meta: string + try { + meta = readFileSync(metaAbs, 'utf8') + } catch { + // No record yet: a brand-new pair is recorded by the author's --write + // after review, never silently by regeneration. + return false + } + // The record must be exactly the well-formed two-entry shape for THIS pair; + // a malformed or renamed-key sidecar is the pairing gate's problem to + // report, never something regeneration silently repairs into validity. + const recorded = parsePairMeta(meta) + const names = [pageRel, zhRel].map(rel => rel.split('/').at(-1) ?? rel) + if (!recorded || recorded.size !== 2 || !names.every(name => recorded.has(name))) return false + for (const rel of [pageRel, zhRel]) { + const previous = before.get(rel) + if (!previous) return false + if (recorded.get(rel.split('/').at(-1) ?? rel) !== blobHash(previous)) return false + const current = readFileSync(resolve(scanRoot, rel)) + const strippedBefore = partitionGeneratedRegions(previous.toString('utf8')).stripped + const strippedAfter = partitionGeneratedRegions(current.toString('utf8')).stripped + if (strippedBefore !== strippedAfter) return false + } + const source = readFileSync(resolve(scanRoot, pageRel)) + const zh = readFileSync(resolve(scanRoot, zhRel)) + writeFileSync(metaAbs, renderPairMeta(pageRel, blobHash(source), zhRel, blobHash(zh))) + return true +} + +/** CLI entry: default regenerates every artifact, `--check` fails if any is + * stale. Guarded behind an entry-point check so importing this module for + * tests neither regenerates the committed files nor calls process.exit. * @returns nothing; writes files or reports freshness through the process. */ export function main(): void { - const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY) const outputs: [string, string][] = [ - [OUT_EVENTS, renderEvents([...model.events], CORDIS_CATALOG_POLICY)], - [OUT_SERVICES, renderServices([...model.services], CORDIS_CATALOG_POLICY)], - [OUT_RUNTIME_API, projector.renderRuntimeApi(model)], + ...computeOutputs(), ...renderCordisCoreApiPages(), ] if (process.argv.includes('--check')) { @@ -369,25 +657,51 @@ export function main(): void { try { committed = readFileSync(resolve(root, out), 'utf8') } catch { - // Only ENOENT is expected; either read failure has the same remedy. + // Only ENOENT (not yet generated) is expected; a present-but-unreadable + // 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 } if (committed !== content) stale.push(out) } if (stale.length === 0) { - console.log(`gen-cordis-catalog: ${outputs.length} generated file(s) are up to date.`) + console.log(`gen-cordis-catalog: ${outputs.length} generated file(s)/region(s) are up to date.`) process.exit(0) } - console.error(`gen-cordis-catalog: ${stale.join(' and ')} ${stale.length === 1 ? 'is' : 'are'} stale. Run \`pnpm run gen-cordis-catalog\` and commit the result.`) + console.error(`gen-cordis-catalog: stale — ${stale.join(', ')}. Run \`pnpm run gen-cordis-catalog\` and commit the result.`) process.exit(1) } + const before = new Map() + for (const [out] of outputs) { + try { + before.set(out, readFileSync(resolve(root, out))) + } catch { + // First generation of this artifact; nothing to guard, nothing to record. + } + } + let changedPages = 0 + let recorded = 0 for (const [out, content] of outputs) { const destination = resolve(root, out) + if (before.get(out)?.toString('utf8') === content) continue mkdirSync(dirname(destination), { recursive: true }) writeFileSync(destination, content) + changedPages++ } - console.log(`gen-cordis-catalog: wrote ${outputs.length} generated file(s).`) + for (const page of [...new Set([...Object.values(SERVICE_PAGE), ...Object.values(EVENT_SCOPE_PAGE)])]) { + const rel = `${SUBSYSTEMS_DIR}/${page}` + const zhRel = rel.replace(/\.md$/, '.zh.md') + const wroteEither = [rel, zhRel].some((side) => { + const previous = before.get(side) + return previous !== undefined && previous.toString('utf8') !== readFileSync(resolve(root, side), 'utf8') + }) + if (wroteEither && maybeRecordPair(rel, before)) recorded++ + } + console.log(`gen-cordis-catalog: ${outputs.length} artifact(s) computed, ${changedPages} written, ${recorded} pair record(s) refreshed.`) } -if (process.argv[1] && import.meta.filename === resolve(process.argv[1])) main() +// 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() +} diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts index 4f72b1d953..1c4cce0fa3 100644 --- a/scripts/gen-doc-graphs.ts +++ b/scripts/gen-doc-graphs.ts @@ -1344,7 +1344,7 @@ function renderIndex(docs: GraphDoc[]): string { const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode' return [ ...generatedHeader('Documentation Graph Index'), - 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the generated [events](cordis-catalog/events.md) / [services](cordis-catalog/services.md) catalogs, [tool-catalog.md](tool-catalog.md), and [subsystems/](subsystems/core.md).', + 'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the [subsystem pages](subsystems/core.md) (types + the generated `cordis-surface` regions) and [tool-catalog.md](tool-catalog.md).', '', 'The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md).', '', diff --git a/scripts/gen-persistence-catalog.ts b/scripts/gen-persistence-catalog.ts index 800497ddb7..5294242e27 100644 --- a/scripts/gen-persistence-catalog.ts +++ b/scripts/gen-persistence-catalog.ts @@ -352,7 +352,7 @@ export function render(events: AnnotatedLogEventEntry[], envelopeTypes: EventEnv '', '# Session Persistence Event Catalog', '', - 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the [cordis events catalog](cordis-catalog/events.md) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', + 'Every event type that can appear in a session\'s durable event log: the complete persisted `SessionEvent` envelope and each member of the merge-extensible `SessionEventMap` — the owning vocabulary in `@deepseek-ai/dsh-session` plus every plugin declaration merge in this repo — with source JSDoc, full payload declaration, surface badge, and declaration site. It complements [session.md](subsystems/session.md) (surface ordering and the `deriveMessages()` projection), [persistence.md](subsystems/persistence.md) (how the log is made durable), and the generated region of [session.md](subsystems/session.md#cordis-surface) (the live bus wiring — a log event is NOT a cordis event; it reaches listeners via the single `session/event` emit).', '', 'This file is GENERATED from source (`scripts/gen-persistence-catalog.ts`) and verified fresh by `pnpm run verify-persistence-catalog` (part of `doc-sync`) — do not edit it by hand. Declaration blocks retain the source declaration and nested property JSDoc, removing only the indentation imposed by a containing interface/module, and use a `ts persistence-catalog` fence (skipped by doc-typecheck because declarations reference types from their owning modules). Type names in a payload link to the page that documents them. See [the persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md).', '', diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts index 39ed4d7620..a68ab874f0 100644 --- a/scripts/gen-tool-catalog.ts +++ b/scripts/gen-tool-catalog.ts @@ -608,7 +608,7 @@ export function render(catalog: ToolCatalog): string { '', '# Tool Schema Catalog', '', - 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the cordis [events](cordis-catalog/events.md) & [services](cordis-catalog/services.md) catalogs (the wiring a plugin listens to and calls) and [subsystems/](subsystems/core.md) (the types those signatures move) — this page is the *tools* the agent is offered.', + 'Every model-facing tool a shipped plugin contributes to `ctx.tools`: the `name`, `description`, and JSON-Schema `parameters` the model receives via the system-prompt assembly. It complements the [subsystem pages](subsystems/core.md) (the types plus each page\'s generated `cordis-surface` wiring region) — this page is the *tools* the agent is offered.', '', 'This file is GENERATED and verified fresh by `pnpm run verify-tool-catalog` (part of `doc-sync`) — do not edit it by hand. Unlike the cordis catalog (a pure source-AST pass), this generator BOOTS each tool plugin on a real context and reads `ctx.tools.schemas()`, because a tool schema is not statically knowable (runtime-spread enums, concatenated descriptions, config-driven names, raw-JSON-Schema MCP tools). A completeness guard globs `packages/*/tool-*` and fails if any package is missing from the generator\'s boot manifest, so a new tool cannot be silently undocumented. See [the tool-schema-catalog Agent Note](../.agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md).', '', diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index ebdce88f6d..c2ca4136d3 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -276,7 +276,7 @@ describe('docsPages locale routes', () => { const translated = rootPages.filter(page => page.contentLocale === 'zh-CN') const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US') - expect(translated).toHaveLength(20) + expect(translated).toHaveLength(37) expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true) expect(fallbacks.map(page => page.source).sort()).toEqual([ 'docs/subsystems/commands.md', @@ -286,11 +286,11 @@ describe('docsPages locale routes', () => { }) it('publishes the Cordis core API under matching locale structures', () => { - const files = ['context.md', 'events.md', 'fiber.md', 'registry.md', 'service.md'] + const files = ['context.md', 'events.md', 'fiber.md', 'registry.md', 'service.md', 'inherited.md'] for (const file of files) { const root = docsPages.find(page => page.route === `reference/cordis-api/${file}`) const english = docsPages.find(page => page.route === `en/reference/cordis-api/${file}`) - expect(root?.source).toBe(`docs/cordis-catalog/core/${file}`) + expect(root?.source).toBe(`docs/cordis-api/${file}`) expect(root?.section).toBe('Cordis API') expect(english?.source).toBe(root?.source) expect(english?.section).toBe('Cordis Core API') diff --git a/scripts/project-doc-site.ts b/scripts/project-doc-site.ts index e3397237a3..55c7204d78 100644 --- a/scripts/project-doc-site.ts +++ b/scripts/project-doc-site.ts @@ -131,6 +131,12 @@ function destinationRange(rawNode: string, type: 'link' | 'image' | 'definition' return { start, end: rawNode.length } } +// `#fragment` suffixes pass through verbatim. Generated cordis-surface +// headings carry explicit `` anchors with the GitHub slug, so those +// fragments resolve on the published site too; hand-written headings rely on +// VitePress's own slugger, which differs from GitHub's for punctuation-heavy +// text — hand-authored cross-page fragments should prefer plain-text headings +// or explicit anchors. function splitTarget(url: string): { path: string; suffix: string } { const boundary = url.search(/[?#]/) if (boundary === -1) return { path: url, suffix: '' } diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 7c77929ba3..112cd3ae4d 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -7,7 +7,7 @@ "docs/agent-lifecycle.md", "docs/capability-seams.md", "docs/config-catalog.md", - "docs/cordis-catalog/", + "docs/cordis-api/", "docs/event-producer-consumer.md", "docs/graph-atlas.md", "docs/i18n/style-samples.md", diff --git a/scripts/translation-pairing.spec.ts b/scripts/translation-pairing.spec.ts index 1073495d08..b89df38f52 100644 --- a/scripts/translation-pairing.spec.ts +++ b/scripts/translation-pairing.spec.ts @@ -12,11 +12,13 @@ import { translationPairPaths, } from './translation-pairing-record.ts' import { + blobHash, isTranslationScopeFile, pairAnchorOfArgument, parseTranslationMarkdown, parseTranslationPairingCliArgs, parseTranslationPairingManifest, + partitionGeneratedRegions, translationStructureDiff, translationStructureSignature, } from './translation-pairing.ts' @@ -292,3 +294,42 @@ describe('pair CLI arguments', () => { expect(() => parseTranslationPairingCliArgs(['--cached', '--write', 'docs/foo.md'])).toThrow('read-only') }) }) + +describe('generated regions', () => { + const BEGIN = '' + const END = '' + + it('partitions marker-delimited regions from the hand-owned remainder', () => { + const doc = `# T\n\nprose\n\n${BEGIN}\ninjected\n${END}\ntail\n` + const { regions, stripped } = partitionGeneratedRegions(doc) + expect(regions).toEqual([`${BEGIN}\ninjected\n${END}`]) + expect(stripped).toBe('# T\n\nprose\n\ntail\n') + }) + + it('treats a document without markers as one hand-owned remainder', () => { + const { regions, stripped } = partitionGeneratedRegions('# T\n\nprose\n') + expect(regions).toEqual([]) + expect(stripped).toBe('# T\n\nprose\n') + }) + + it('rejects unbalanced or nested markers', () => { + expect(() => partitionGeneratedRegions(`${END}\n`)).toThrow('without a BEGIN') + expect(() => partitionGeneratedRegions(`${BEGIN}\n`)).toThrow('without an END') + expect(() => partitionGeneratedRegions(`${BEGIN}\n${BEGIN}\n${END}\n`)).toThrow('nested') + }) + + it('rejects mismatched slugs and malformed marker lines', () => { + expect(() => partitionGeneratedRegions('\nx\n\n')) + .toThrow("END slug 'b' does not match its BEGIN slug 'a'") + expect(() => partitionGeneratedRegions(' trailing\nx\n\n')) + .toThrow('malformed generated region marker line') + expect(() => partitionGeneratedRegions('x\n tail\n')) + .toThrow('malformed generated region marker line') + }) + + it('computes the exact git blob hash', () => { + // `git hash-object` of the empty file and of "x\n" — pinned upstream values. + expect(blobHash(Buffer.from(''))).toBe('e69de29bb2d1d6434b8b29ae775ad8c2e48c5391') + expect(blobHash(Buffer.from('x\n'))).toBe('587be6b4c3f93f93c489c0111bba5596147a26cb') + }) +}) diff --git a/scripts/translation-pairing.ts b/scripts/translation-pairing.ts index 6d6893f843..417e836578 100644 --- a/scripts/translation-pairing.ts +++ b/scripts/translation-pairing.ts @@ -2,13 +2,122 @@ * Pure parsing and structural helpers for the bilingual-document pairing * gate. Kept separate from the CLI so corpus discovery and signature behavior * can be regression-tested without reading or mutating the repository tree. + * Also the one home of the generated-region grammar and the pair-record + * primitives, shared by the pairing gate and the region-injecting generators. */ +import { createHash } from 'node:crypto' +import { basename } from 'node:path' import { fromMarkdown } from 'mdast-util-from-markdown' import { gfmFromMarkdown } from 'mdast-util-gfm' import { gfm } from 'micromark-extension-gfm' import type { Nodes } from 'mdast' +/** Complete opening marker line: `` (slug captured). */ +const GENERATED_REGION_BEGIN_LINE = /^$/ +/** Complete closing marker line: `` (slug captured). */ +const GENERATED_REGION_END_LINE = /^$/ +/** Loose marker detector: any line that LOOKS like a region marker must parse as one. */ +const GENERATED_REGION_MARKER_HINT = /^ diff --git a/docs/subsystems/compaction.zh.md b/docs/subsystems/compaction.zh.md index 24c55f0624..63b28518e6 100644 --- a/docs/subsystems/compaction.zh.md +++ b/docs/subsystems/compaction.zh.md @@ -224,7 +224,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null pruneSession(session: Session): PruneResult ``` -Types: [ContentBlock](core.md) · [Session](session.md) +Types: [ContentBlock](llm-streaming.md) · [Session](session.md) Source: [`packages/compact/compact-tool-result-prune/src/index.ts:44`](../../packages/compact/compact-tool-result-prune/src/index.ts) diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 0994de32ec..16c8e29cf2 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: 8f27727d63e598fbf31b09a87ab53ad686155b4f -core.zh.md: fff5bd502cbed6f2167eb23731fb5c3825efb7af +core.md: ba9319d377cf250148a145ba3f1fa1baa967c858 +core.zh.md: 276dc4f99f1f802e66aca050b2fbcdc178931611 diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 8f27727d63..ba9319d377 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -1,20 +1,29 @@ -# Core Data Structures +# Core English | [中文](core.zh.md) -This page catalogs the **core** data structures of the DeepSeek Harness — what each spine type represents, its literal shape, and where the full detail lives. It complements [architecture.md](../architecture.md), which describes *behavior* (the service map, the session/turn/step lifecycle, the event taxonomy); this page describes the *vocabulary* that behavior moves around. The sibling subsystem pages are indexed in the [folder README](README.md). +The **core** subsystem is [`packages/core`](../../packages/core/README.md) — the control spine every composition boots: the event-sourced session log, system-prompt assembly, the tool registry, the agent vocabulary, and the concrete loop that drives them. This page owns the spine's shared vocabulary — the `Agent` handle, its delivery and interception contracts, and the repo-wide type patterns — and orients to the group's dedicated pages; the folder is indexed in the [subsystems README](README.md). -## What counts as "core" +## The spine, package by package -The harness is a microkernel: a tiny core plus many plugins. Most types belong to one plugin or one capability. A handful, though, are the **spine** — the language the agent loop and its events traffic in on *every* turn, no matter which optional plugins are loaded. Those are "core". +A turn flows through the six packages in one loop: the driver in [`agent-loop`](../../packages/core/agent-loop) claims a queued prompt, opens a turn on the [session log](session.md) (`ctx.sessions`), assembles the request prefix through [system-prompt](system-prompt.md) (`ctx.systemPrompt`) and derives history from the log, streams the model response through the [LLM seam](llm-streaming.md), dispatches tool calls through the [tool registry](tools.md) (`ctx.tools`), and appends every model-visible fact back onto the log before the next step derives from it. -Precisely, a data structure is **core** if either: +| Package | Owns | Page | +|---|---|---| +| `session/` | The append-only `SessionEvent` log and in-memory store — the single source of truth (`ctx.sessions`) | [session.md](session.md) | +| `system-prompt/` | Prompt-section and tool-schema assembly (`ctx.systemPrompt`) | [system-prompt.md](system-prompt.md) | +| `tools/` | The scoped tool registry and guarded execution pipeline (`ctx.tools`) | [tools.md](tools.md) | +| `agent/` | The `Agent` interface, live registry, initiator scope, and `agent/*` event vocabulary (`ctx.agents`) | this page | +| `agent-loop/` | The concrete driver implementing the public `Agent` contract (`ctx.agentLoop`) | this page | +| `scope/` | The scoped-registration primitive the registries and loop build per-agent scoping on | [scope.md](scope.md) | -1. it flows through the agent-loop spine — the loop holds it, derives it, streams it, or logs it on every turn (a `Message`, a `StreamChunk`, a `SessionEvent`, the `Agent` handle itself), independent of which plugins are present; **or** -2. it is the single headline type a plugin author writes against a pipeline — `ToolDefinition` (what every tool *is*). +`scope/` is the one non-service package: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) that sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle. `agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; it runs each driver inside `ctx.agents.withInitiator()`. Extension plugins depend on `agent` — including when they need the initiating Agent — and never on `agent-loop` directly, so the loop stays swappable. The default composition that wires this spine into a runnable agent is [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md). -Everything else is documented on its own subsystem page, not here. The rule that draws the line: *the type you write, hold, or receive is core; the machinery that types it, renders it, or persists it is another page’s detail.* So `ToolDefinition` is core, but the `ValueSchemaSpec`/`ParameterSchemaSpec` inference machinery that types it, the `ToolCallView`/`ToolResultView` render-intent vocabulary that renders it, and the `SessionPersistence` seam that stores the event log are not — they live on their own pages ([index](README.md)). + +## What this page owns + +The conversation vocabulary the loop moves — `Message`, `ContentBlock`, `StreamChunk`, the model request — is declared by [`packages/llm`](../../packages/llm/README.md) and documented on [llm-streaming.md](llm-streaming.md); the session-event, prompt-assembly, and tool vocabularies live on this group's dedicated pages above. What remains here is the vocabulary shared by everything: the `Agent` handle and its delivery, cancellation, and interception contracts (declared by `packages/core/agent`), the `SessionEvent` envelope, and the two type patterns every subsystem follows. The scoping rule is recorded in the [subsystems-catalog Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md): the type you write, hold, or receive is documented where its declaring subsystem is; the machinery that types, renders, or persists it stays on that machinery's page. ## The `…Map → derived-union` pattern @@ -37,13 +46,14 @@ declare module '@deepseek-ai/dsh-llm' { } ``` -Five canonical maps use this pattern; a plugin author extends these: +Six canonical maps use this pattern; a plugin author extends these: | Map | Package | Derives | Catalog | |---|---|---|---| -| `ContentBlockMap` | dsh-llm | `ContentBlock` | [below](#content-blocks-and-messages) | -| `MessageSourceMap` | dsh-llm | `MessageSource` | [below](#content-blocks-and-messages) | -| `FinishReasonMap` | dsh-llm | `FinishReason` | [below](#the-model-request-and-result) | +| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | +| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | @@ -64,458 +74,6 @@ type Branded = string & { readonly [BRAND]: B } The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `TaskId` in [tasks.md](tasks.md). -## Content blocks and messages - -A conversation is `Message`s; a message is an array of typed **content blocks**. The block union derives from `ContentBlockMap`. - -Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ToolCallBlock` (`id: CallId`, `name`, raw-JSON `arguments`), `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the merge-extensible map together with the adapter/UI/compaction support that honors it. - -Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) - -A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages carry provider/model ownership and optional adapter-private replay metadata in their source: - -```ts type-equiv -/** Provider ownership and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { - /** Provider route that produced the message. */ - provider: string - /** Provider model id that produced the message. */ - model: string - /** - * Lossless-JSON adapter state needed to replay the provider response. - * `LlmService` exposes it to a target adapter only when that adapter instance - * currently owns both this historical provider and the target provider. - */ - replayState?: unknown -} -``` - -```ts type-equiv -/** One immutable message representation shared by delivery, durable history, and model requests. */ -interface Message { - /** Stable identity preserved across every representation boundary. */ - readonly id: MessageId - /** Provider-neutral conversation role. */ - readonly role: 'system' | 'user' | 'assistant' - /** Exact model-facing blocks. */ - readonly content: ContentBlock[] - /** Required producer provenance. */ - readonly source: MessageSource -} -``` - -Where a message came from is itself a merge-extensible sum type: - -```ts type-equiv -/** - * Where a message (or injected content) came from. - * Merge-extensible sum type — plugins add their own `kind`s. - */ -interface MessageSourceMap { - user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } & ContextFormed - model: ModelMessageSource - tool: ToolMessageSource -} -``` - -Provenance and shape are two independent axes. `kind` answers *who produced this*; the optional `form` a producer mixes in answers *what shape of information it is*, so several producers may share one presentation and one producer may emit more than one shape over a session. The vocabulary is semantic and grows one value at a time; an absent or unrecognized value is the documented default, presented as opaque content: - -```ts type-equiv -/** - * What SHAPE of information a producer-supplied context carries, declared by - * the producer beside its provenance. - * - * `MessageSource.kind` answers *who produced this*; `form` answers *what kind - * of thing it is*, and the two axes are deliberately independent — several - * producers share one form (three snapshot producers today), and one producer - * may emit more than one form over a session. - * - * The vocabulary is SEMANTIC, never visual: a value states that the content is - * a file's instructions or a catalog of available items, and a consumer decides - * what that looks like. Colors, icons, ordering, and collapse defaults are the - * consumer's business and must not enter this union. It grows one value at a - * time as producers gain the structured fields their form needs; an absent or - * unknown value is the documented default, presented as opaque content. - */ -type ContextForm = - /** Instructions read out of workspace files the model is expected to follow. */ - | 'instructions' - /** A catalog of items available in this session, republished as it changes. */ - | 'catalog' - /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ - | 'snapshot' - /** A one-off account of something that just happened; it supersedes nothing. */ - | 'notice' - /** A message another agent addressed to this one. */ - | 'relay' - /** Material lifted out of another session's log, possibly reduced on the way in. */ - | 'recall' -``` - -```ts type-equiv -/** One named contribution to a `snapshot`-form context, in assembly order. */ -interface ContextSnapshotSection { - /** The contributing subsystem's name. */ - readonly name: string - /** That contribution's model-facing text, exactly as assembled. */ - readonly text: string -} -``` - -```ts type-equiv -/** - * Producer-declared {@link ContextForm} and the fields that form requires, - * mixed into the source shapes that carry one. - * - * Discriminated by `form` so a producer cannot declare a shape without the - * facts that shape is presented from: a `notice` must record its one-line - * account, a `snapshot` its sections. Omitting `form` stays valid — an - * undeclared context is the documented default. - */ -type ContextFormed = - | { readonly form?: never } - | { readonly form: 'instructions' } - | { readonly form: 'catalog' } - | { - readonly form: 'snapshot' - /** The named contributions this snapshot assembled, in order. */ - readonly sections: readonly ContextSnapshotSection[] - } - | { - readonly form: 'notice' - /** One-line account of what happened, shown without expanding the row. */ - readonly summary: string - } - | { readonly form: 'relay' } - | { readonly form: 'recall' } -``` - -## Streaming - -Adapters emit a raw **chunk** protocol; the loop logs the chunks (replay fidelity) while feeding the same chunks through a `BlockAssembler` to rebuild blocks and messages. `StreamChunk` is a closed discriminated union over `type` — `block-start`, `text-delta`, `reasoning-delta`, `tool-call-delta`, `block-end`, `usage`, `finish`. - -The full union, the adapter contract (usage-before-finish, raw-JSON tool arguments, the two sanctioned error paths), and `BlockAssembler` live on **[llm-streaming.md](llm-streaming.md)**. - - - -## The model request - -One model call is a fully-assembled `GenerateOptions`. The adapter answers with a raw `StreamChunk` stream; the consumer assembles it with `BlockAssembler` (see [llm-streaming.md](llm-streaming.md)). - -Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -Provider and model discovery uses small provider-neutral descriptors. A model catalog is advisory: routing still keys on a registered provider, and an adapter may accept unlisted model ids. - -Registering an adapter returns a handle: the disposer, plus the atomic route replacement a plugin whose route set is user-configurable needs. - -```ts type-equiv -/** - * What {@link LlmService.registerAdapter} returns: the disposer, plus an - * atomic route replacement for the same adapter instance. - */ -interface AdapterRegistrationHandle { - /** Release every route this registration currently holds. */ - (): void - /** - * Replace this registration's routes with `providers`, keeping the same - * adapter instance. The candidate set is validated in full first — a - * conflict with another adapter, an invalid name, or bad provider metadata - * throws and leaves the current routes untouched — and the swap itself is - * one synchronous section, so no request can observe a gap. An empty array - * is legal here (a settings section that emptied holds zero routes while - * staying registered), unlike an empty initial registration. - * - * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration - * has been released: its routes are gone and its disposer has already run, - * so anything registered afterwards would have no owner left to release it. - * @param providers - the complete next route set for this registration. - */ - replace(providers: string[]): void -} -``` - -```ts type-equiv -/** Display metadata for one registered provider route. */ -interface LlmProviderInfo { - /** Provider route key used by {@link GenerateOptions.provider}. */ - id: string - /** Human-readable provider name for selectors and diagnostics. */ - name: string -} -``` - -Adapter plugins additionally declare which routes *could* run through `registerConfigurableProviders()`, addressing each one's user-settings section, so configuration surfaces can offer dormant providers before any route registers. - -```ts type-equiv -/** - * One provider route an adapter plugin can activate through configuration, - * whether or not the route is currently registered. Configuration surfaces - * merge this directory with `listProviders()` to offer every configurable - * provider alongside its live/dormant state. - */ -interface LlmConfigurableProvider { - /** Provider route key this entry activates when configured. */ - provider: string - /** Human-readable provider name for configuration surfaces. */ - displayName: string - /** User-settings namespace whose section configures this provider. */ - settingsNs: string - /** - * Path from that namespace's section root to this provider's profile - * object; empty when the whole section is the profile. - */ - settingsPath: readonly string[] -} -``` - -```ts type-equiv -/** One adapter-discovered model; catalog membership is advisory, not request validation. */ -interface LlmModelInfo { - /** Provider route that owns this model entry. */ - provider: string - /** Model id passed to {@link GenerateOptions.model}. */ - id: string - /** Human-readable model name for selectors. */ - name: string - /** Optional user-facing distinction from otherwise similar models. */ - description?: string -} -``` - -A provider a surface is still drafting has no route and no catalog, so interrogation is described separately: the request carries the draft the user is editing, and the reply is candidates a surface may adopt rather than a catalog it must serve. - -```ts type-equiv -/** - * One interrogation of a provider endpoint that configuration has not stored - * yet. Configuration surfaces send the draft a user is still editing, so the - * request carries the endpoint and credential directly instead of naming a - * route: a provider being added has no route to name. - */ -interface LlmModelDiscoveryRequest { - /** - * Route the draft is editing, when it edits an existing one. A route whose - * adapter already knows its models answers from that knowledge instead of - * asking the endpoint — the adapter's own registry is the better answer, and - * it costs no network call. - */ - provider?: string - /** - * Endpoint to interrogate. Optional because a route the adapter already - * describes needs none; a route it does not must supply one. - */ - baseURL?: string - /** Wire protocol the endpoint speaks, when the draft names one. */ - api?: string - /** Credential for this interrogation alone; the harness never stores it. */ - apiKey?: string - /** Caller cancellation; implementations must settle promptly after it aborts. */ - signal?: AbortSignal -} -``` - -```ts type-equiv -/** - * One model an endpoint reports about itself. Every field but the id is - * optional because most provider listings disclose an id and nothing else; - * a surface adopting one of these still owes the capacities its adapter needs. - */ -interface LlmDiscoveredModel { - /** Model id the endpoint accepts. */ - id: string - /** Human-readable name when the endpoint supplies one. */ - name?: string - /** Maximum combined request and response context, when disclosed. */ - contextWindow?: number - /** Maximum output tokens, when disclosed. */ - maxTokens?: number -} -``` - -Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, and reasoning choices share one exact-model result so consumers do not repeat authoritative model resolution. - -```ts type-equiv -/** Provider-owned context capacity for one exact provider/model route. */ -interface LlmModelContext { - /** Maximum combined request and response context in tokens. */ - contextWindow: number -} -``` - -Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the ordered set, display names, and optional deployment default. - -```ts type-equiv -/** Adapter-owned identifier for one model's selectable reasoning effort. */ -type ReasoningEffortId = Branded<'ReasoningEffortId'> -``` - -```ts type-equiv -/** Display metadata for one adapter-owned reasoning effort. */ -interface LlmReasoningEffortInfo { - /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ - id: ReasoningEffortId - /** Human-readable effort name for selectors and diagnostics. */ - name: string - /** Optional user-facing distinction from otherwise similar efforts. */ - description?: string -} -``` - -```ts type-equiv -/** Selectable reasoning efforts for one exact provider/model route. */ -interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ - efforts: readonly LlmReasoningEffortInfo[] - /** - * Adapter-configured default materialized into requests when callers omit - * an effort. Absence preserves the provider's own default. - */ - defaultEffort?: ReasoningEffortId -} -``` - -```ts type-equiv -/** Exact-route model metadata resolved by its owning adapter. */ -interface LlmResolvedModelInfo extends LlmModelInfo { - /** Provider-owned context capacity when known. */ - context?: LlmModelContext - /** Adapter-configured per-request output cap materialized when callers omit one. */ - defaultMaxTokens?: number - /** Adapter-owned selectable reasoning levels when exposed. */ - reasoning?: LlmModelReasoningInfo -} -``` - -```ts type-equiv -/** A single model request, fully assembled. */ -interface GenerateOptions { - /** Registered provider route selecting the adapter instance. */ - provider: string - model: string - /** Adapter-owned reasoning effort selected for this exact model. */ - reasoningEffort?: ReasoningEffortId - /** - * Ordered conversation messages, exactly as the provider sees them (after - * the `system` slot). A loop-built request assembles them as - * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. - */ - messages: Message[] - /** System prompt text (adapters map to the provider's system slot). */ - system?: string - /** Tool schemas (adapters map to the provider's `tools` field). */ - tools?: ToolSchema[] - temperature?: number - maxTokens?: number - /** - * Stop sequences: generation halts as soon as the model produces any one of - * these strings (adapters map to the provider's stop field, e.g. OpenAI - * `stop`). The stop string itself is not included in the output. - */ - stop?: string[] - signal?: AbortSignal - /** - * Session identity stamped by the loop for listener routing. Adapters ignore - * it; replay uses it to keep concurrent parent and child cursors independent. - */ - sessionId?: Branded<'SessionId'> - /** - * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. - */ - purpose?: 'compaction' | 'session-title' -} -``` - -Why a model response stopped is a merge-extensible reason. Terminal provider failures carry the streaming contract's [`LlmFailure`](llm-streaming.md#llmfailure): - -```ts type-equiv -/** - * Why a model response stopped. - * Merge-extensible so adapters can surface provider-specific reasons. - */ -interface FinishReasonMap { - 'stop': { kind: 'stop' } - 'tool-calls': { kind: 'tool-calls' } - 'max-tokens': { kind: 'max-tokens' } - 'aborted': { kind: 'aborted'; failure: LlmFailure } - 'error': { kind: 'error'; failure: LlmFailure } -} -``` - -`FinishReason = FinishReasonMap[keyof FinishReasonMap]`. `TokenUsage` (per-call accounting with disjoint cache fields) is detailed on [llm-streaming.md](llm-streaming.md). - -`GenerateOptions.tools` carries `ToolSchema` — the JSON-schema description of a tool, as sent to the model. It is declared in dsh-llm (not dsh-tools) precisely because it is part of the request the loop assembles every step: - -```ts type-equiv -/** - * JSON-schema description of a tool, as sent to the model. - * - * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; - * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import - * it from this package. - */ -interface ToolSchema { - name: string - description: string - /** JSON Schema object for the arguments. */ - parameters: Record -} -``` - -The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition` that produces it (schema + `execute`) is on [tools.md](tools.md). - -### The request envelope: `LlmCallConfig` and the logged header - -The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md). - -`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus provenance under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests. - -On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request. - -```ts type-equiv -/** - * Provider, model, reasoning effort, and sampling scalars of one conversation's - * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; - * the loop builds requests from the logged header rather than accepting these - * per call. - */ -interface LlmCallConfig { - provider: string - model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] -} -``` - -```ts type-equiv -/** - * Effective config fields supplied by exact-model adapter resolution rather - * than by the caller's request proposal. - */ -interface LlmCallConfigAdapterDefaults { - reasoningEffort?: true - maxTokens?: true -} -``` - ## Sessions A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. The event vocabulary derives from `SessionEventMap`: @@ -531,7 +89,7 @@ Source: [`packages/core/session/src/types.ts`](../../packages/core/session/src/t * * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`). + * `assistant/message`, `tool/result`, `steering/message`). * Non-surface events (boundary markers, chunks, usage, errors) never carry * surface metadata — the compiler enforces this at `Session.append()` * call sites. @@ -559,7 +117,7 @@ type SessionEvent = { }[T] ``` -The session event variants, `deriveMessages()` projection rules, `TurnEndReason` vocabulary, and execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. +The twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. ## The agent handle @@ -568,11 +126,72 @@ The session event variants, `deriveMessages()` projection rules, `TurnEndReason` Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) ```ts type-equiv -/** One of the two ordered pending-message lists owned by an agent. */ -type InboxTarget = 'next-turn' | 'next-step' +/** + * Which inbox queue a {@link Agent.send} item joins: + * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. + * - `next-step` — during prompt admission or an open turn, the item stages for + * the next safe step boundary; otherwise it is promoted per its `wakeup` + * flag. + */ +type SendTarget = 'next-turn' | 'next-step' ``` -Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.append`, `prepend`, `replace`, `remove`, `clear`, `splice`, and `claim` record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists; replacement may change identity and emits the old message as discarded followed by the new message as inserted. Ordinary removals and `clear()` are cancellations. `claim(target)` removes the proposed step batch — all `next-step` input plus, at a turn boundary, one `next-turn` message — through pure deletion splices without emitting discarded notifications, and the loop separately emits per-message claimed notifications. Whole-queue consumers such as UI projections reconstruct `nextTurn` and `nextStep` from the durable splices, while consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications. +```ts type-equiv +/** Resolved inbox placement reported when an accepted message is enqueued. */ +type InboxPlacement = 'queued' | 'steering' +``` + +`InboxItemId` is a process-local branded string minted for each accepted FIFO occurrence. It is intentionally distinct from `MessageId`: sending the same immutable message twice creates two independently addressable pending items. + +```ts type-equiv +/** One independently addressable accepted occurrence in an agent inbox. */ +interface InboxItem { + /** Agent-loop-minted occurrence identity. */ + readonly id: InboxItemId + /** Identified message delivered by the caller. */ + readonly message: UserMessage + /** Acceptance-time FIFO classification. */ + readonly placement: InboxPlacement +} +``` + +```ts type-equiv +/** A user-requested mutation of one still-pending queued occurrence. */ +type InboxAction = + | { readonly kind: 'edit'; readonly content: ContentBlock[] } + | { readonly kind: 'remove' } + | { readonly kind: 'steer' } +``` + +```ts type-equiv +/** Result of applying an inbox action at the synchronous ownership boundary. */ +type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' +``` + +```ts type-equiv +/** + * Options for the unified {@link Agent.send} primitive over the + * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} + * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and + * {@link Agent.inject} (`next-step`/no-wakeup). + * + * The object is complete so routing policy is explicit. + */ +interface SendOptions { + /** Queue the item joins. */ + target: SendTarget + /** + * Whether this item makes the model run: wake a parked driver (`next-turn`) + * or force a continuation step (`next-step` while running). A `false` + * `next-turn` item queues without waking; a `false` + * `next-step` item attaches durable context without forcing another step + * (the injection preset). + */ + wakeup: boolean +} +``` + +The fixed-preset aliases own `target` and `wakeup`; their already identified `UserMessage` carries role, content, and provenance. Its `MessageId` remains stable when an edit replaces content or strict steer transfers the immutable message. The original queued occurrence ends and strict steer accepts a new steering occurrence with a distinct `InboxItemId`. Injection bypasses the FIFOs and never appears on inbox lifecycle events. ```ts type-equiv /** Options for {@link Agent.cancel}. */ @@ -580,25 +199,28 @@ interface CancelOptions { /** * Preserve queued and steering inbox items instead of discarding them. The * active turn is still aborted, but un-started and pending work survives for a - * later turn and no canceled inbox splice is logged. + * later turn and no `agent/inbox/discard` fires. */ - keepInbox?: boolean | undefined + keepInbox?: boolean } ``` +`SteeringReceipt.outcome` always resolves. `admitted` identifies the turn and step whose immutable request history contains that exact message; `rejected` means lifecycle or terminal policy discarded it first. Synchronous input validation still throws from `steer()`. + ```ts type-equiv -/** Why an active agent driver was cancelled. */ +/** Stable runtime cause accepted by {@link Agent.cancel}. */ type AgentCancelCause = | { readonly kind: 'user' } | { readonly kind: 'parent' } - | { readonly kind: 'hook'; readonly reason: string } - | { readonly kind: 'disposed' } ``` -`Agent` is an interface over the public live-agent contract. Its unified `send` method exposes target and wakeup routing directly; `followup`, `steer`, and `inject` are fixed-preset aliases. +`Agent` is an interface over the public live-agent contract. Concrete drivers own the `followup`/`steer`/`inject` aliases and route them through `send`'s (`target` × `wakeup`) matrix. ```ts type-equiv -/** Public live-agent handle. */ +/** + * Public live-agent handle with aliases over the unified delivery primitive. + * @typert object + */ interface Agent { /** The single identity shared with {@link session}. */ readonly id: SessionId @@ -606,28 +228,78 @@ interface Agent { readonly options: AgentOptions /** The live session this agent drives; its log is the durable source of truth. */ readonly session: Session - /** The agent-owned projection of durable pending work. */ - readonly inbox: Inbox /** The current lifecycle state, mirrored on every `agent/status` transition. */ readonly status: AgentStatus + /** + * Whether a `next-step` send currently stages for prompt admission or the + * open turn. Unlike {@link status}, this excludes admission exit and turn + * settlement, when a waking `next-step` send becomes a queued follow-up. + */ + readonly acceptsNextStep: boolean /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ readonly ctx: Context + /** + * The unified delivery primitive over the (`target` × `wakeup`) matrix. + * It routes the caller's typed content and source as follows: + * + * - `next-turn` queues an item that becomes the sole ordinary message of its + * own FIFO-ordered turn; `wakeup:true` wakes a + * parked driver, while `wakeup:false` queues without waking. + * - `next-step` with `wakeup:true` stages steering during prompt admission + * or an open turn; outside that window it falls back to a woken + * `next-turn`. + * - `next-step` with `wakeup:false` injects durable model-facing context + * without running the model: admission or an open turn stages it for the + * next safe log position, while an injection outside that window appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. + * The agent publishes or queues the identified frozen message as-is. + * @param message - identified model-facing content and its producer provenance. + * @param options - target queue and wakeup decision. + */ + send(message: UserMessage, options: SendOptions): void + + /** + * Reserve admission of the next ordinary turn while this agent is idle, so an + * operation can mutate durable history before any queued prompt derives a + * request from it. Already-accepted waking work has right of way, including a + * send whose wake is still a pending microtask. Later sends keep their + * ordinary placement, FIFO order, and `wakeup` facts, and + * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes + * a queued follow-up rather than steering; cancellation and disposal may + * still discard them. {@link inject} is not withheld. {@link whenIdle} treats + * a live reservation as activity, while lifecycle teardown does not await it. + * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. + */ + reserveTurnAdmission(): (() => void) | undefined + + /** + * Mutate one still-pending queued occurrence synchronously. Editing preserves + * the message identity and queue position; removal publishes its terminal + * discard. Steer strictly transfers the message into the current next-step + * window, or returns `steer-unavailable` without changing the queued + * occurrence. Steering occurrences and driver-claimed items return + * `not-found`. + * @param id - independently addressable queued occurrence. + * @param action - edit, remove, or strict steer operation. + * @returns the applied outcome or the reason no mutation occurred. + */ + updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult + /** * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn or between-turn task. The first cause wins for that activity. With no - * active activity, cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the active operation signal. + * turn. An effective call first emits `agent/cancel-requested` with the + * resolved typed cause. The first cause wins for the active turn, and + * `whenIdle()` resolves after cancellation reaches quiescence. Idle + * cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the current turn signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ cancel(cause: AgentCancelCause, options?: CancelOptions): void - /** - * Resolve after the current whole-agent activity reaches quiescence. This - * follows replacement work started before the observed driver retires, - * but does not identify the settlement of any particular message. - * @returns fulfillment after no active driver or maintenance task remains. - */ + /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ whenIdle(): Promise /** @@ -662,29 +334,35 @@ interface Agent { followup(message: UserMessage): void /** - * Submit steering for the nearest step. An idle driver starts a turn; - * a running driver consumes it at its next step boundary. - * A rejected step leaves steering parked in the inbox until the next - * wake; cancellation or disposal may discard pending steering. + * Submit steering with a message-owned admission receipt — the + * `next-step`/wakeup preset of {@link send}. During prompt admission or an + * open turn, the message waits in the steering FIFO until a committed step + * snapshots it; outside that window it enters the ordinary queued FIFO. The + * receipt resolves `admitted` only after the message joins that step's + * immutable request history, or `rejected` when terminal policy, + * cancellation, or disposal discards it first. A non-terminal turn close may + * leave it staged for a later admitted prompt without settling the receipt. * @param message - identified steering content and its producer provenance. + * @returns the receipt for this exact message's eventual admission outcome. */ - steer(message: UserMessage): void + steer(message: UserMessage): SteeringReceipt /** - * Queue model-facing context for the next pre-step without waking the - * driver. A running driver claims it at the nearest later step boundary; - * idle drivers leave it pending until follow-up or steering - * wakes them. It may miss a request whose pre-step already claimed its - * batch. Cancellation or disposal may discard pending context. + * Append model-facing context without running the model — the + * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn + * stages it at the next safe log position; outside that window it appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. * @param message - identified injected context and its producer provenance. */ inject(message: UserMessage): void } ``` -`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `followup()` returns no handle: its `MessageId` identifies durable inbox insertion, claim, and discard facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)). `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default. +`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `acceptsNextStep` is the narrower routing predicate for callers that must choose between steering the current admission/turn and submitting a fresh admitted prompt. A live turn-admission reservation is quiescence-relevant without changing `status` or turning later queue entries into steering; its only authority is to defer the next driver claim until release. `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default. -The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. +The cause is a TypeScript-enforced same-process input. An active `TurnCancellation` holder copies its discriminant into the runtime-only `AbortSignal.reason` and is retired before `turn/end` publication; the frozen `AbortSignal.reason` remains readable after that retirement. Only the loop reads the cause (`user`, `parent`, or lifecycle-only `disposed`) back off its own machine-private signal at settlement — there is no public reader, and a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits. @@ -694,19 +372,22 @@ The process-local initiator carried by `ctx.agents` is the exact `Agent` above, ## Interception decisions -Pre-step decisions use the same identified `UserMessage` shape as durable user-role input. The entered batch is authoritative and preserves every message's identity and provenance. Hook bridges map their native decision fields onto this typed result. +Prompt and post-tool decisions use the same identified `UserMessage` shape as durable user-role input. Each `additionalContexts` entry becomes a separate `user/message`, preserving its identity and provenance. Hook bridges map their native decision fields onto these typed results. Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) -`agent/pre-step` receives one payload carrying the exclusive claimed batch (`messages`), the proposed step's coordinates (`turn`, `step`), and the current turn's cancellation `signal`. The initial proposal runs inside an open turn before any step; a tool continuation may submit an empty claimed batch between steps: - -It returns a `PreStepDecision`. Reject opens no step. Enter supplies the complete message batch appended after `step/start`; claimed messages omitted by the final decision remain removed, while input inserted after the claim stays pending: +`agent/prompt-submit` returns a `PromptDecision` before a turn opens. Allow may rewrite the claimed prompt or attach `additionalContexts`; block rejects admission without creating turn events: ```ts type-equiv -/** Whether and with which messages the loop enters a proposed step. */ -type PreStepDecision = - | { kind: 'reject' } - | { kind: 'enter'; messages: UserMessage[] } +/** + * Prompt interception result. `allow.content` replaces the prompt, while + * `additionalContexts` appends model-facing context before the turn starts. + * An `allow` returned by a listener is authoritative: a listener wrapping + * `next()` preserves both fields unless it intentionally replaces them. + */ +type PromptDecision = + | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } + | { kind: 'block'; reason: string } ``` `agent/request-error` runs after a failed model step closes and before its turn closes. Listeners can repair durable state or await policy work while the failed turn's signal is still live. A handling listener returns `{ kind: 'retry' }` without calling `next()`; the default `undefined` leaves the failure terminal. @@ -716,7 +397,12 @@ type PreStepDecision = type RequestErrorAction = { kind: 'retry' } | undefined ``` -`agent/pre-step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. +```ts type-equiv +/** Model-request failure with an optional machine-routable provider code. */ +type RequestError = Error & { code?: string } +``` + +`agent/step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. `agent/session-start` carries a `SessionStartSource` (why the session lifecycle began; a bridge keys its SessionStart matcher on it): @@ -776,7 +462,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -954,6 +640,29 @@ Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/i ### `agent/*` events + + +#### `agent/cancel-requested` — emit + +Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. + +```ts cordis-catalog +/** + * Effective broad cancellation was requested, before queued/outbox work + * is cleared or the active turn is aborted. This observe-only notification + * cannot veto cancellation; listener failures are contained. + * @param agent - the agent whose current work is being cancelled. + * @param cause - the explicit typed cancellation cause. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) + #### `agent/created` — emit @@ -967,11 +676,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param payload.agent - the newly registered agent with its live session and completed setup. + * @param agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, payload: { agent: Agent }): void +'agent/created'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -989,11 +698,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param payload.agent - the exact agent removed from the registry. + * @param agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +'agent/disposed'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -1004,111 +713,138 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here even when - * the error has no in-turn position for a durable record. - * @param payload.agent - the agent whose turn errored. - * @param payload.turn - the turn in which the failure surfaced. - * @param payload.step - the step at which the failure surfaced. - * @param payload.error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here (plus the + * logger) even when the error has no in-turn position for a durable record. + * @param agent - the agent whose turn errored. + * @param turn - the turn in which the failure surfaced. + * @param step - the step at which the failure surfaced. + * @param error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/claimed` — emit +#### `agent/inbox/dequeue` — emit -One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. +The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. ```ts cordis-catalog /** - * One message left the inbox inside its open turn. If the proposed step - * is rejected, the claimed message ends here: it is neither discarded nor - * re-emitted as a user/message, and the turn closes without a step. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the claimed message. - * @param payload.turn - the owning turn. + * The driver claimed one item out of the inbox: a queued item at a turn + * boundary, or steering drained between steps. Fires after the item leaves + * its FIFO and before it becomes a durable message. + * @param agent - the agent whose inbox item was claimed. + * @param item - the exact claimed occurrence. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discarded` — emit +#### `agent/inbox/discard` — emit -One message was discarded from the live inbox. +Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. ```ts cordis-catalog /** - * One message was discarded from the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the discarded message. + * Pending inbox items were dropped without delivering them, so every + * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR + * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, + * emits this after `agent/cancel-requested` when applicable and before + * aborting the active work. Fires once per drop with every dropped item. + * @param agent - the agent whose inbox items were dropped. + * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/inserted` — emit +#### `agent/inbox/enqueue` — emit -One message entered the live inbox. +An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. ```ts cordis-catalog /** - * One message entered the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the inserted message. + * An item entered the queued or steering inbox. `placement` is the + * acceptance-time routing result; listeners must not reconstruct it from + * later agent or session state. + * @param agent - the owning agent. + * @param item - accepted occurrence, message, and resolved placement. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/pre-step` — waterfall +#### `agent/inbox/update` — emit -Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. +A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. ```ts cordis-catalog /** - * Reject a proposed step or replace the messages that enter it. Calling - * `next()` preserves the current messages. - * @param payload.agent - the agent proposing the step. - * @param payload.messages - messages removed from the inbox for this step. - * @param payload.turn - the turn that will own the step. - * @param payload.step - the step proposed by the loop. - * @param payload.signal - the current turn's cancellation signal. + * A still-pending queued item changed content. The item id, placement, and + * position remain stable while the event carries the replacement message. + * @param agent - the owning agent. + * @param item - the complete post-update occurrence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) + + + +#### `agent/prompt-submit` — waterfall + +Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. + +```ts cordis-catalog +/** + * Allow, rewrite, or block one claimed prompt before it becomes a user + * message or opens a turn. Call `next()` for the unchanged default. The + * signal controls only this admission attempt; listeners may cooperate with + * it but must not retain it for a later attempt or turn. + * @param agent - the agent whose turn claimed the message. + * @param message - the frozen claimed message, including identity and source. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -1127,17 +863,17 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param payload.agent - the agent making the model call. - * @param payload.turn - the open turn number. - * @param payload.step - the step whose request this is. - * @param payload.signal - the current turn's explicit abort signal. + * @param agent - the agent making the model call. + * @param turn - the open turn number. + * @param step - the step whose request this is. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise ``` -Types: [Scoped](scope.md) +Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) @@ -1145,25 +881,28 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle one failed model-request attempt before the loop retries or closes - * its step. A listener returns `{ kind: 'retry' }` without calling `next()` - * when it owns recovery, or calls `next()` to delegate. The default - * `undefined` leaves the failure terminal. - * @param payload.agent - the agent whose request failed. - * @param payload.turn - the turn containing the failed request. - * @param payload.step - the step containing the failed request attempt. - * @param payload.provider - the provider selected for the failed request. - * @param payload.failure - serializable facts normalized at the final adapter boundary. - * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. - * @param payload.signal - the turn abort signal. + * Handle a model-request failure after its failed step has closed but + * before the failed turn closes. A listener returns `{ kind: 'retry' }` + * without calling `next()` when it owns the error, or calls `next()` to + * delegate. The default `undefined` leaves the failure terminal. + * @param agent - the agent whose request failed. + * @param turn - the open turn number. + * @param step - the failed step number. + * @param error - the original model-request failure. + * @param failure - serializable facts normalized at the final adapter boundary. + * @param priorFailures - immutable failures that already authorized another + * retry turn in this consecutive sequence. + * @param retryPolicy - immutable policy of the adapter registration that served + * the failed request, or `undefined` if no final adapter served it. + * @param signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -1182,12 +921,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param payload.agent - the agent whose session lifecycle began. - * @param payload.source - why the session started (fresh startup, resume, …). + * @param agent - the agent whose session lifecycle began. + * @param source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void ``` Types: [Scoped](scope.md) @@ -1198,19 +937,18 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. +Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). A waking delivery enters - * `running` synchronously after reserving cancellation; `idle` means no - * driver remains scheduled or active. - * @param payload.agent - the agent whose status flipped. - * @param payload.status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). `send()` does not enter + * `running` synchronously; drive lifecycle from this event. + * @param agent - the agent whose status flipped. + * @param status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void ``` Types: [Scoped](scope.md) @@ -1221,7 +959,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. ```ts cordis-catalog /** @@ -1231,17 +969,14 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. The conclusion - * never short-circuits already-submitted next-step work: same-step - * `additionalContexts` or racing steering still runs, and the turn - * closes only when that inbox drains. - * @param payload.agent - the agent whose turn is at its stop boundary. - * @param payload.turn - the turn about to close. - * @param payload.signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. + * @param agent - the agent whose turn is at its stop boundary. + * @param turn - the turn about to close. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void ``` Types: [Scoped](scope.md) @@ -1264,12 +999,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param payload.sessionId - exact shared agent/session identity that failed startup. - * @param payload.error - persistence, setup, or publication failure. + * @param sessionId - exact shared agent/session identity that failed startup. + * @param error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void ``` -Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index fff5bd502c..276dc4f99f 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -1,22 +1,29 @@ -# 核心数据结构 +# 核心 [English](core.md) | 中文 -本页编目 DeepSeek Harness 的**核心**数据结构:每个主干类型代表什么、它的字面形状,以及完整细节在哪里。它与 [architecture.md](../architecture.md) 互补——后者描述*行为*(服务映射、会话/轮次/步骤生命周期、事件分类体系);本页描述行为所操作的*词汇*。同级的各子系统页面见[目录 README](README.md)。 +**核心**子系统即 [`packages/core`](../../packages/core/README.md)——每个组合都会启动的控制主干:事件溯源的会话日志、系统提示词组装、工具注册表、agent 词汇,以及驱动它们的具体循环。本页拥有主干的共享词汇——`Agent` 句柄及其投递与拦截契约,以及全仓通用的类型模式——并指引到该组的专属页面;目录索引见[子系统 README](README.md)。 -## 什么算"核心" +## 主干逐包速览 -harness 是一个微内核:一个极小的核心加上众多插件。大多数类型属于某一个插件或某一项能力。但有少数类型构成**主干**——agent loop(智能体循环)及其事件在*每一个*轮次中使用的语言,无论加载了哪些可选插件。这些就是"核心"。 +一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的 prompt,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。 -精确地说,一个数据结构是**核心**的,当且仅当满足以下条件之一: +| 包 | 负责内容 | 页面 | +|---|---|---| +| `session/` | 只追加的 `SessionEvent` 日志与内存 store——唯一事实来源(`ctx.sessions`) | [session.md](session.md) | +| `system-prompt/` | 提示词段落与工具 schema 组装(`ctx.systemPrompt`) | [system-prompt.md](system-prompt.md) | +| `tools/` | 带作用域的工具注册表与受保护的执行流水线(`ctx.tools`) | [tools.md](tools.md) | +| `agent/` | `Agent` 接口、实时注册表、发起者作用域与 `agent/*` 事件词汇(`ctx.agents`) | 本页 | +| `agent-loop/` | 实现公开 `Agent` 契约的具体 driver(`ctx.agentLoop`) | 本页 | +| `scope/` | 注册表与循环用于构建按 agent 作用域的注册原语 | [scope.md](scope.md) | -1. 它流经 agent loop 主干——循环在每个轮次中持有、派生、流式输出或记录它(`Message`、`StreamChunk`、`SessionEvent`、`Agent` 句柄本身),与当前加载了哪些插件无关;**或者** -2. 它是插件作者面向某条流水线编写的代表性类型——`ToolDefinition`(每个工具*是什么*)。 +`scope/` 是这里唯一的非服务包:一个零依赖库(`createScope`/`scopeOf`/`scopeTarget`),在模块图中位于 `session/` 与 `system-prompt/` 之下,正是为了让它们消费它而不形成环。`agent-loop` 是 `agent` seam 的唯一具体实现,放在这里因为它是 harness 的默认产品循环;它在 `ctx.agents.withInitiator()` 内运行每个 driver。扩展插件依赖 `agent`——包括需要发起 Agent 时——而绝不直接依赖 `agent-loop`,因此循环保持可替换。把这条主干接成可运行 agent 的默认组合是 [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md)。 -其他一切都记录在各自的子系统页面上,而非本页。划线的规则是:*你编写、持有或接收的类型是核心;为它提供类型推导、渲染或持久化的机制是其他页面的细节*。因此 `ToolDefinition` 是核心,但为它提供类型推导的 `ValueSchemaSpec`/`ParameterSchemaSpec` 机制、为它提供渲染意图的 `ToolCallView`/`ToolResultView` 词汇,以及存储事件日志的 `SessionPersistence` seam 都不是——它们各有自己的页面([目录](README.md))。 + +## 本页拥有什么 - +循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md);会话事件、提示词组装与工具词汇在上表该组的专属页面。留在本页的是所有部分共享的词汇:`Agent` 句柄及其投递、取消与拦截契约(由 `packages/core/agent` 声明)、`SessionEvent` 信封,以及每个子系统都遵循的两个类型模式。范围界定规则记录在[子系统目录 Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md):你编写、持有或接收的类型记录在其声明子系统所在之处;为它提供类型推导、渲染或持久化的机制留在该机制自己的页面。 ## `…Map → derived-union` 模式 @@ -39,13 +46,14 @@ declare module '@deepseek-ai/dsh-llm' { } ``` -五个规范 map 使用此模式;插件作者扩展它们: +六个规范 map 使用此模式;插件作者扩展它们: | Map | 包 | 派生 | 目录 | |---|---|---|---| -| `ContentBlockMap` | dsh-llm | `ContentBlock` | [下文](#content-blocks-and-messages) | -| `MessageSourceMap` | dsh-llm | `MessageSource` | [下文](#content-blocks-and-messages) | -| `FinishReasonMap` | dsh-llm | `FinishReason` | [下文](#the-model-request-and-result) | +| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | +| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | @@ -68,460 +76,6 @@ type Branded = string & { readonly [BRAND]: B } 两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [tasks.md](tasks.md) 中的 `TaskId`。 - - -## 内容块与消息 - -一段对话由 `Message` 组成;一条消息是一个类型化**内容块**的数组。块的联合类型从 `ContentBlockMap` 派生。 - -源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`)、`ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。核心集仅限于每条交付路径都尊重的块——多模态内容(图像、音频等)没有核心块类型;需要的功能通过可合并扩展的 map 添加,同时提供适配器/UI/压缩支持。 - -源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) - -`Message` 是一个带标识且不可变的角色/来源/内容值。模型产生的 assistant 消息会在其来源中携带提供方/模型所有权与可选的适配器私有回放元数据: - -```ts type-equiv -/** Provider ownership and adapter-private replay data for an assistant message. */ -interface AssistantProvenance { - /** Provider route that produced the message. */ - provider: string - /** Provider model id that produced the message. */ - model: string - /** - * Lossless-JSON adapter state needed to replay the provider response. - * `LlmService` exposes it to a target adapter only when that adapter instance - * currently owns both this historical provider and the target provider. - */ - replayState?: unknown -} -``` - -```ts type-equiv -/** One immutable message representation shared by delivery, durable history, and model requests. */ -interface Message { - /** Stable identity preserved across every representation boundary. */ - readonly id: MessageId - /** Provider-neutral conversation role. */ - readonly role: 'system' | 'user' | 'assistant' - /** Exact model-facing blocks. */ - readonly content: ContentBlock[] - /** Required producer provenance. */ - readonly source: MessageSource -} -``` - -消息来源本身也是一个可合并扩展的和类型: - -```ts type-equiv -/** - * Where a message (or injected content) came from. - * Merge-extensible sum type — plugins add their own `kind`s. - */ -interface MessageSourceMap { - user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } & ContextFormed - model: ModelMessageSource - tool: ToolMessageSource -} -``` - -溯源与形态是相互独立的两根轴。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现: - -```ts type-equiv -/** - * What SHAPE of information a producer-supplied context carries, declared by - * the producer beside its provenance. - * - * `MessageSource.kind` answers *who produced this*; `form` answers *what kind - * of thing it is*, and the two axes are deliberately independent — several - * producers share one form (three snapshot producers today), and one producer - * may emit more than one form over a session. - * - * The vocabulary is SEMANTIC, never visual: a value states that the content is - * a file's instructions or a catalog of available items, and a consumer decides - * what that looks like. Colors, icons, ordering, and collapse defaults are the - * consumer's business and must not enter this union. It grows one value at a - * time as producers gain the structured fields their form needs; an absent or - * unknown value is the documented default, presented as opaque content. - */ -type ContextForm = - /** Instructions read out of workspace files the model is expected to follow. */ - | 'instructions' - /** A catalog of items available in this session, republished as it changes. */ - | 'catalog' - /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ - | 'snapshot' - /** A one-off account of something that just happened; it supersedes nothing. */ - | 'notice' - /** A message another agent addressed to this one. */ - | 'relay' - /** Material lifted out of another session's log, possibly reduced on the way in. */ - | 'recall' -``` - -```ts type-equiv -/** One named contribution to a `snapshot`-form context, in assembly order. */ -interface ContextSnapshotSection { - /** The contributing subsystem's name. */ - readonly name: string - /** That contribution's model-facing text, exactly as assembled. */ - readonly text: string -} -``` - -```ts type-equiv -/** - * Producer-declared {@link ContextForm} and the fields that form requires, - * mixed into the source shapes that carry one. - * - * Discriminated by `form` so a producer cannot declare a shape without the - * facts that shape is presented from: a `notice` must record its one-line - * account, a `snapshot` its sections. Omitting `form` stays valid — an - * undeclared context is the documented default. - */ -type ContextFormed = - | { readonly form?: never } - | { readonly form: 'instructions' } - | { readonly form: 'catalog' } - | { - readonly form: 'snapshot' - /** The named contributions this snapshot assembled, in order. */ - readonly sections: readonly ContextSnapshotSection[] - } - | { - readonly form: 'notice' - /** One-line account of what happened, shown without expanding the row. */ - readonly summary: string - } - | { readonly form: 'relay' } - | { readonly form: 'recall' } -``` - -## 流式输出 - -适配器发出原始**分片**协议;循环记录分片(回放保真度),同时将同一批分片送入 `BlockAssembler` 以重建块和消息。`StreamChunk` 是基于 `type` 的封闭判别联合——`block-start`、`text-delta`、`reasoning-delta`、`tool-call-delta`、`block-end`、`usage`、`finish`。 - -完整联合类型、适配器契约(usage-before-finish、原始 JSON 工具参数、两条认可的错误路径)和 `BlockAssembler` 在 **[llm-streaming.md](llm-streaming.md)** 中。 - - - -## 模型请求 - -一次模型调用是一个完全组装好的 `GenerateOptions`。适配器以原始 `StreamChunk` 流作答;消费方用 `BlockAssembler` 组装它(见 [llm-streaming.md](llm-streaming.md))。 - -源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) - -提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。 - -注册适配器会返回一个句柄:既是释放器,也带有原子的路由替换——路由集合由用户配置决定的插件正需要它。 - -```ts type-equiv -/** - * What {@link LlmService.registerAdapter} returns: the disposer, plus an - * atomic route replacement for the same adapter instance. - */ -interface AdapterRegistrationHandle { - /** Release every route this registration currently holds. */ - (): void - /** - * Replace this registration's routes with `providers`, keeping the same - * adapter instance. The candidate set is validated in full first — a - * conflict with another adapter, an invalid name, or bad provider metadata - * throws and leaves the current routes untouched — and the swap itself is - * one synchronous section, so no request can observe a gap. An empty array - * is legal here (a settings section that emptied holds zero routes while - * staying registered), unlike an empty initial registration. - * - * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration - * has been released: its routes are gone and its disposer has already run, - * so anything registered afterwards would have no owner left to release it. - * @param providers - the complete next route set for this registration. - */ - replace(providers: string[]): void -} -``` - -```ts type-equiv -/** Display metadata for one registered provider route. */ -interface LlmProviderInfo { - /** Provider route key used by {@link GenerateOptions.provider}. */ - id: string - /** Human-readable provider name for selectors and diagnostics. */ - name: string -} -``` - -适配器插件还会通过 `registerConfigurableProviders()` 声明哪些路由*可以*运行,并指明每条路由的用户设置分节,使配置界面能在任何路由注册之前就呈现休眠的提供方。 - -```ts type-equiv -/** - * One provider route an adapter plugin can activate through configuration, - * whether or not the route is currently registered. Configuration surfaces - * merge this directory with `listProviders()` to offer every configurable - * provider alongside its live/dormant state. - */ -interface LlmConfigurableProvider { - /** Provider route key this entry activates when configured. */ - provider: string - /** Human-readable provider name for configuration surfaces. */ - displayName: string - /** User-settings namespace whose section configures this provider. */ - settingsNs: string - /** - * Path from that namespace's section root to this provider's profile - * object; empty when the whole section is the profile. - */ - settingsPath: readonly string[] -} -``` - -```ts type-equiv -/** One adapter-discovered model; catalog membership is advisory, not request validation. */ -interface LlmModelInfo { - /** Provider route that owns this model entry. */ - provider: string - /** Model id passed to {@link GenerateOptions.model}. */ - id: string - /** Human-readable model name for selectors. */ - name: string - /** Optional user-facing distinction from otherwise similar models. */ - description?: string -} -``` - -界面正在起草的提供方既没有路由也没有 catalog,因此询问被单独描述:请求携带用户正在编辑的草稿,回复是界面可以采纳的候选,而不是它必须服务的 catalog。 - -```ts type-equiv -/** - * One interrogation of a provider endpoint that configuration has not stored - * yet. Configuration surfaces send the draft a user is still editing, so the - * request carries the endpoint and credential directly instead of naming a - * route: a provider being added has no route to name. - */ -interface LlmModelDiscoveryRequest { - /** - * Route the draft is editing, when it edits an existing one. A route whose - * adapter already knows its models answers from that knowledge instead of - * asking the endpoint — the adapter's own registry is the better answer, and - * it costs no network call. - */ - provider?: string - /** - * Endpoint to interrogate. Optional because a route the adapter already - * describes needs none; a route it does not must supply one. - */ - baseURL?: string - /** Wire protocol the endpoint speaks, when the draft names one. */ - api?: string - /** Credential for this interrogation alone; the harness never stores it. */ - apiKey?: string - /** Caller cancellation; implementations must settle promptly after it aborts. */ - signal?: AbortSignal -} -``` - -```ts type-equiv -/** - * One model an endpoint reports about itself. Every field but the id is - * optional because most provider listings disclose an id and nothing else; - * a surface adopting one of these still owes the capacities its adapter needs. - */ -interface LlmDiscoveredModel { - /** Model id the endpoint accepts. */ - id: string - /** Human-readable name when the endpoint supplies one. */ - name?: string - /** Maximum combined request and response context, when disclosed. */ - contextWindow?: number - /** Maximum output tokens, when disclosed. */ - maxTokens?: number -} -``` - -对正确性敏感的元数据与参考目录分开解析,并归服务该确切路由的适配器所有。上下文容量、适配器调用默认值和推理选项共用同一个确切模型结果,消费方因而无需重复执行权威模型解析。 - -```ts type-equiv -/** Provider-owned context capacity for one exact provider/model route. */ -interface LlmModelContext { - /** Maximum combined request and response context in tokens. */ - contextWindow: number -} -``` - -推理强度是另一项针对确切路由的能力。核心为标识符添加品牌类型,但不枚举其值;有序集合、展示名称和可选的部署默认值均由各适配器持有。 - -```ts type-equiv -/** Adapter-owned identifier for one model's selectable reasoning effort. */ -type ReasoningEffortId = Branded<'ReasoningEffortId'> -``` - -```ts type-equiv -/** Display metadata for one adapter-owned reasoning effort. */ -interface LlmReasoningEffortInfo { - /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ - id: ReasoningEffortId - /** Human-readable effort name for selectors and diagnostics. */ - name: string - /** Optional user-facing distinction from otherwise similar efforts. */ - description?: string -} -``` - -```ts type-equiv -/** Selectable reasoning efforts for one exact provider/model route. */ -interface LlmModelReasoningInfo { - /** Supported efforts in adapter-preferred display order. */ - efforts: readonly LlmReasoningEffortInfo[] - /** - * Adapter-configured default materialized into requests when callers omit - * an effort. Absence preserves the provider's own default. - */ - defaultEffort?: ReasoningEffortId -} -``` - -```ts type-equiv -/** Exact-route model metadata resolved by its owning adapter. */ -interface LlmResolvedModelInfo extends LlmModelInfo { - /** Provider-owned context capacity when known. */ - context?: LlmModelContext - /** Adapter-configured per-request output cap materialized when callers omit one. */ - defaultMaxTokens?: number - /** Adapter-owned selectable reasoning levels when exposed. */ - reasoning?: LlmModelReasoningInfo -} -``` - -```ts type-equiv -/** A single model request, fully assembled. */ -interface GenerateOptions { - /** Registered provider route selecting the adapter instance. */ - provider: string - model: string - /** Adapter-owned reasoning effort selected for this exact model. */ - reasoningEffort?: ReasoningEffortId - /** - * Ordered conversation messages, exactly as the provider sees them (after - * the `system` slot). A loop-built request assembles them as - * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. - */ - messages: Message[] - /** System prompt text (adapters map to the provider's system slot). */ - system?: string - /** Tool schemas (adapters map to the provider's `tools` field). */ - tools?: ToolSchema[] - temperature?: number - maxTokens?: number - /** - * Stop sequences: generation halts as soon as the model produces any one of - * these strings (adapters map to the provider's stop field, e.g. OpenAI - * `stop`). The stop string itself is not included in the output. - */ - stop?: string[] - signal?: AbortSignal - /** - * Session identity stamped by the loop for listener routing. Adapters ignore - * it; replay uses it to keep concurrent parent and child cursors independent. - */ - sessionId?: Branded<'SessionId'> - /** - * Provider-neutral classification for an auxiliary model call. Adapters may - * map the purpose to model-hidden transport metadata or purpose-specific - * generation policy. Ordinary conversation requests leave it unset. - */ - purpose?: 'compaction' | 'session-title' -} -``` - -模型响应为何停止由可合并扩展的原因表示。提供方终态失败携带流式契约的 [`LlmFailure`](llm-streaming.md#llmfailure): - -```ts type-equiv -/** - * Why a model response stopped. - * Merge-extensible so adapters can surface provider-specific reasons. - */ -interface FinishReasonMap { - 'stop': { kind: 'stop' } - 'tool-calls': { kind: 'tool-calls' } - 'max-tokens': { kind: 'max-tokens' } - 'aborted': { kind: 'aborted'; failure: LlmFailure } - 'error': { kind: 'error'; failure: LlmFailure } -} -``` - -`FinishReason = FinishReasonMap[keyof FinishReasonMap]`。`TokenUsage`(逐调用计量,含不相交的缓存字段)详见 [llm-streaming.md](llm-streaming.md)。 - -`GenerateOptions.tools` 携带 `ToolSchema`——工具的 JSON Schema 描述,发送给模型。它声明在 dsh-llm(而非 dsh-tools)中,正是因为它是循环每一步组装请求的一部分: - -```ts type-equiv -/** - * JSON-schema description of a tool, as sent to the model. - * - * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; - * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import - * it from this package. - */ -interface ToolSchema { - name: string - description: string - /** JSON Schema object for the arguments. */ - parameters: Record -} -``` - -面向模型的 `ToolSchema` 是协议格式;产出它的已注册 `ToolDefinition`(schema + `execute`)在 [tools.md](tools.md) 中。 - -### 请求信封:`LlmCallConfig` 与记录的 header - -循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。 - -`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置及其来源。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。 - -在协议格式上,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 `user/message`,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。 - -```ts type-equiv -/** - * Provider, model, reasoning effort, and sampling scalars of one conversation's - * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; - * the loop builds requests from the logged header rather than accepting these - * per call. - */ -interface LlmCallConfig { - provider: string - model: string - reasoningEffort?: ReasoningEffortId - temperature?: number - maxTokens?: number - stop?: string[] -} -``` - -```ts type-equiv -/** - * Effective config fields supplied by exact-model adapter resolution rather - * than by the caller's request proposal. - */ -interface LlmCallConfigAdapterDefaults { - reasoningEffort?: true - maxTokens?: true -} -``` - ## 会话 `Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM(大语言模型)消息历史从日志*派生*(`deriveMessages()`),而非单独存储。事件词汇从 `SessionEventMap` 派生: @@ -537,7 +91,7 @@ interface LlmCallConfigAdapterDefaults { * * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`). + * `assistant/message`, `tool/result`, `steering/message`). * Non-surface events (boundary markers, chunks, usage, errors) never carry * surface metadata — the compiler enforces this at `Session.append()` * call sites. @@ -565,7 +119,7 @@ type SessionEvent = { }[T] ``` -会话事件变体、`deriveMessages()` 投影规则、`TurnEndReason` 词汇以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 +十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 @@ -576,11 +130,72 @@ type SessionEvent = { 源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) ```ts type-equiv -/** One of the two ordered pending-message lists owned by an agent. */ -type InboxTarget = 'next-turn' | 'next-step' +/** + * Which inbox queue a {@link Agent.send} item joins: + * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. + * - `next-step` — during prompt admission or an open turn, the item stages for + * the next safe step boundary; otherwise it is promoted per its `wakeup` + * flag. + */ +type SendTarget = 'next-turn' | 'next-step' ``` -每个待处理入队项就是其 `UserMessage`;`MessageId` 是唯一标识。`Inbox.append`、`prepend`、`replace`、`remove`、`clear`、`splice` 与 `claim` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。`replace(messageId, newMessage)` 与 `remove(messageId)` 通过 `MessageId` 跨两份列表定位待处理消息;替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。普通删除和 `clear()` 都表示取消。`claim(target)` 通过无 outcome 的纯删除 splice 移除拟进入步骤的批次——全部 `next-step` 输入,外加轮次边界上的一条 `next-turn` 消息——且不发出 discarded 通知;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` 与 `nextStep`,而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知。 +```ts type-equiv +/** Resolved inbox placement reported when an accepted message is enqueued. */ +type InboxPlacement = 'queued' | 'steering' +``` + +`InboxItemId` 是为每次获准进入 FIFO 的项铸造的进程本地品牌字符串。它有意区别于 `MessageId`:同一条不可变消息发送两次,会创建两个可独立寻址的待处理项。 + +```ts type-equiv +/** One independently addressable accepted occurrence in an agent inbox. */ +interface InboxItem { + /** Agent-loop-minted occurrence identity. */ + readonly id: InboxItemId + /** Identified message delivered by the caller. */ + readonly message: UserMessage + /** Acceptance-time FIFO classification. */ + readonly placement: InboxPlacement +} +``` + +```ts type-equiv +/** A user-requested mutation of one still-pending queued occurrence. */ +type InboxAction = + | { readonly kind: 'edit'; readonly content: ContentBlock[] } + | { readonly kind: 'remove' } + | { readonly kind: 'steer' } +``` + +```ts type-equiv +/** Result of applying an inbox action at the synchronous ownership boundary. */ +type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' +``` + +```ts type-equiv +/** + * Options for the unified {@link Agent.send} primitive over the + * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} + * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and + * {@link Agent.inject} (`next-step`/no-wakeup). + * + * The object is complete so routing policy is explicit. + */ +interface SendOptions { + /** Queue the item joins. */ + target: SendTarget + /** + * Whether this item makes the model run: wake a parked driver (`next-turn`) + * or force a continuation step (`next-step` while running). A `false` + * `next-turn` item queues without waking; a `false` + * `next-step` item attaches durable context without forcing another step + * (the injection preset). + */ + wakeup: boolean +} +``` + +固定预设的别名方法自带 `target` 与 `wakeup`;其已有标识的 `UserMessage` 会携带角色、内容与 provenance。编辑替换内容或严格 steering(中途引导)转移不可变消息时,其 `MessageId` 都保持稳定。原 queued 单次入队项会结束,严格 steering 则接受一个具有不同 `InboxItemId` 的新 steering 单次入队项。注入绕过两个 FIFO,从不出现在 inbox 生命周期事件中。 ```ts type-equiv /** Options for {@link Agent.cancel}. */ @@ -588,25 +203,28 @@ interface CancelOptions { /** * Preserve queued and steering inbox items instead of discarding them. The * active turn is still aborted, but un-started and pending work survives for a - * later turn and no canceled inbox splice is logged. + * later turn and no `agent/inbox/discard` fires. */ - keepInbox?: boolean | undefined + keepInbox?: boolean } ``` +`SteeringReceipt.outcome` 始终会解析。`admitted` 标识其不可变请求历史包含该确切消息的轮次与步骤;`rejected` 表示生命周期或终止策略先丢弃了该消息。同步输入校验仍会从 `steer()` 抛出异常。 + ```ts type-equiv -/** Why an active agent driver was cancelled. */ +/** Stable runtime cause accepted by {@link Agent.cancel}. */ type AgentCancelCause = | { readonly kind: 'user' } | { readonly kind: 'parent' } - | { readonly kind: 'hook'; readonly reason: string } - | { readonly kind: 'disposed' } ``` -`Agent` 是覆盖公开活跃 agent 契约的接口。它的统一 `send` 方法直接公开目标与唤醒路由;`followup`、`steer` 和 `inject` 是固定预设别名。 +`Agent` 是覆盖公开活跃 agent 契约的接口。具体驱动器拥有 `followup`/`steer`/`inject` 别名方法,并将它们经由 `send` 的(`target` × `wakeup`)矩阵路由。 ```ts type-equiv -/** Public live-agent handle. */ +/** + * Public live-agent handle with aliases over the unified delivery primitive. + * @typert object + */ interface Agent { /** The single identity shared with {@link session}. */ readonly id: SessionId @@ -614,28 +232,78 @@ interface Agent { readonly options: AgentOptions /** The live session this agent drives; its log is the durable source of truth. */ readonly session: Session - /** The agent-owned projection of durable pending work. */ - readonly inbox: Inbox /** The current lifecycle state, mirrored on every `agent/status` transition. */ readonly status: AgentStatus + /** + * Whether a `next-step` send currently stages for prompt admission or the + * open turn. Unlike {@link status}, this excludes admission exit and turn + * settlement, when a waking `next-step` send becomes a queued follow-up. + */ + readonly acceptsNextStep: boolean /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ readonly ctx: Context + /** + * The unified delivery primitive over the (`target` × `wakeup`) matrix. + * It routes the caller's typed content and source as follows: + * + * - `next-turn` queues an item that becomes the sole ordinary message of its + * own FIFO-ordered turn; `wakeup:true` wakes a + * parked driver, while `wakeup:false` queues without waking. + * - `next-step` with `wakeup:true` stages steering during prompt admission + * or an open turn; outside that window it falls back to a woken + * `next-turn`. + * - `next-step` with `wakeup:false` injects durable model-facing context + * without running the model: admission or an open turn stages it for the + * next safe log position, while an injection outside that window appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. + * The agent publishes or queues the identified frozen message as-is. + * @param message - identified model-facing content and its producer provenance. + * @param options - target queue and wakeup decision. + */ + send(message: UserMessage, options: SendOptions): void + + /** + * Reserve admission of the next ordinary turn while this agent is idle, so an + * operation can mutate durable history before any queued prompt derives a + * request from it. Already-accepted waking work has right of way, including a + * send whose wake is still a pending microtask. Later sends keep their + * ordinary placement, FIFO order, and `wakeup` facts, and + * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes + * a queued follow-up rather than steering; cancellation and disposal may + * still discard them. {@link inject} is not withheld. {@link whenIdle} treats + * a live reservation as activity, while lifecycle teardown does not await it. + * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. + */ + reserveTurnAdmission(): (() => void) | undefined + + /** + * Mutate one still-pending queued occurrence synchronously. Editing preserves + * the message identity and queue position; removal publishes its terminal + * discard. Steer strictly transfers the message into the current next-step + * window, or returns `steer-unavailable` without changing the queued + * occurrence. Steering occurrences and driver-claimed items return + * `not-found`. + * @param id - independently addressable queued occurrence. + * @param action - edit, remove, or strict steer operation. + * @returns the applied outcome or the reason no mutation occurred. + */ + updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult + /** * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn or between-turn task. The first cause wins for that activity. With no - * active activity, cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the active operation signal. + * turn. An effective call first emits `agent/cancel-requested` with the + * resolved typed cause. The first cause wins for the active turn, and + * `whenIdle()` resolves after cancellation reaches quiescence. Idle + * cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the current turn signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ cancel(cause: AgentCancelCause, options?: CancelOptions): void - /** - * Resolve after the current whole-agent activity reaches quiescence. This - * follows replacement work started before the observed driver retires, - * but does not identify the settlement of any particular message. - * @returns fulfillment after no active driver or maintenance task remains. - */ + /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ whenIdle(): Promise /** @@ -670,29 +338,35 @@ interface Agent { followup(message: UserMessage): void /** - * Submit steering for the nearest step. An idle driver starts a turn; - * a running driver consumes it at its next step boundary. - * A rejected step leaves steering parked in the inbox until the next - * wake; cancellation or disposal may discard pending steering. + * Submit steering with a message-owned admission receipt — the + * `next-step`/wakeup preset of {@link send}. During prompt admission or an + * open turn, the message waits in the steering FIFO until a committed step + * snapshots it; outside that window it enters the ordinary queued FIFO. The + * receipt resolves `admitted` only after the message joins that step's + * immutable request history, or `rejected` when terminal policy, + * cancellation, or disposal discards it first. A non-terminal turn close may + * leave it staged for a later admitted prompt without settling the receipt. * @param message - identified steering content and its producer provenance. + * @returns the receipt for this exact message's eventual admission outcome. */ - steer(message: UserMessage): void + steer(message: UserMessage): SteeringReceipt /** - * Queue model-facing context for the next pre-step without waking the - * driver. A running driver claims it at the nearest later step boundary; - * idle drivers leave it pending until follow-up or steering - * wakes them. It may miss a request whose pre-step already claimed its - * batch. Cancellation or disposal may discard pending context. + * Append model-facing context without running the model — the + * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn + * stages it at the next safe log position; outside that window it appends + * immediately without opening a turn. If admission closes without a turn, + * a context-only boundary appends immediately; context staged beside + * steering remains pending with it. * @param message - identified injected context and its producer provenance. */ inject(message: UserMessage): void } ``` -`AgentStatus` 为 `'idle' | 'running'`,`SessionId` 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。`followup()` 不返回 handle:其 `MessageId` 标识持久 inbox 的插入、领取与丢弃事实,而不标识之后的助手输出或轮次结束。`whenIdle()` 观察整个 agent,因此只有显式拥有从回执到 idle 这一完整区间的调用方才能将其称为一次运行([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。`AgentOptions` 可合并扩展:core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有:agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。 +`AgentStatus` 为 `'idle' | 'running'`,`SessionId` 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。对于需要在把输入作为 steering 加入当前提示词准入/轮次,还是提交为一个新的待准入提示词之间做选择的调用方,`acceptsNextStep` 才是更窄且准确的路由判断条件。活动的轮次接纳预留与完全停稳相关,但不会改变 `status`,也不会把之后的队列项变成 steering;它的唯一权限是将驱动器的下一次认领延迟到释放时。`AgentOptions` 可合并扩展:core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有:agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。 -cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`;signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 +cause 是由 TypeScript 强制约束的同进程输入。活跃的 `TurnCancellation` 持有者会把其判别字段复制到仅运行时的 `AbortSignal.reason`,并在发布 `turn/end` 前退役;冻结后的 `AbortSignal.reason` 仍可读取。只有 loop 会在结算时从自己机器私有的 signal 上读回 cause(`user`、`parent` 或仅用于生命周期的 `disposed`)——不存在公开的读取器,signal 也不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 [事件分类](../architecture.md#event)拥有 `agent/*` 生命周期、检查点与 waterfall(瀑布式事件)契约。轮次和步骤边界是持久会话事件,而不是 agent emit。 @@ -702,19 +376,22 @@ cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有 ## 拦截决策 -pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。 +提示词决策与工具后决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。每个 `additionalContexts` 条目都会成为一条独立的 `user/message`,保留各自的标识与 provenance。钩子桥接层把其原生决策字段映射到这些类型化结果上。 源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) -`agent/pre-step` 接收一个 payload,携带独占的已领取批次(`messages`)、拟进入步骤的坐标(`turn`、`step`)与当前轮次的取消 `signal`。首次提案在已打开的轮次内、任何步骤开始前运行;工具 continuation 可以在步骤之间提交空的已领取批次: - -它返回 `PreStepDecision`。reject 不会打开步骤。enter 提供在 `step/start` 后追加的完整消息批次;最终决策省略的已领取消息保持已删除,而领取后插入的输入仍留待后续处理: +`agent/prompt-submit` 在轮次打开前返回 `PromptDecision`。allow 可以改写已领取的提示词或附加 `additionalContexts`;block 拒绝准入且不产生任何轮次事件: ```ts type-equiv -/** Whether and with which messages the loop enters a proposed step. */ -type PreStepDecision = - | { kind: 'reject' } - | { kind: 'enter'; messages: UserMessage[] } +/** + * Prompt interception result. `allow.content` replaces the prompt, while + * `additionalContexts` appends model-facing context before the turn starts. + * An `allow` returned by a listener is authoritative: a listener wrapping + * `next()` preserves both fields unless it intentionally replaces them. + */ +type PromptDecision = + | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } + | { kind: 'block'; reason: string } ``` `agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。 @@ -724,7 +401,12 @@ type PreStepDecision = type RequestErrorAction = { kind: 'retry' } | undefined ``` -`agent/pre-step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 +```ts type-equiv +/** Model-request failure with an optional machine-routable provider code. */ +type RequestError = Error & { code?: string } +``` + +`agent/step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 `agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart): @@ -784,7 +466,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -962,6 +644,29 @@ Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/i ### `agent/*` events + + +#### `agent/cancel-requested` — emit + +Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. + +```ts cordis-catalog +/** + * Effective broad cancellation was requested, before queued/outbox work + * is cleared or the active turn is aborted. This observe-only notification + * cannot veto cancellation; listener failures are contained. + * @param agent - the agent whose current work is being cancelled. + * @param cause - the explicit typed cancellation cause. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) + #### `agent/created` — emit @@ -975,11 +680,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param payload.agent - the newly registered agent with its live session and completed setup. + * @param agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, payload: { agent: Agent }): void +'agent/created'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -997,11 +702,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param payload.agent - the exact agent removed from the registry. + * @param agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, payload: { agent: Agent }): void +'agent/disposed'(this: Scoped, agent: Agent): void ``` Types: [Scoped](scope.md) @@ -1012,111 +717,138 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here even when - * the error has no in-turn position for a durable record. - * @param payload.agent - the agent whose turn errored. - * @param payload.turn - the turn in which the failure surfaced. - * @param payload.step - the step at which the failure surfaced. - * @param payload.error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here (plus the + * logger) even when the error has no in-turn position for a durable record. + * @param agent - the agent whose turn errored. + * @param turn - the turn in which the failure surfaced. + * @param step - the step at which the failure surfaced. + * @param error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void +'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/claimed` — emit +#### `agent/inbox/dequeue` — emit -One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. +The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. ```ts cordis-catalog /** - * One message left the inbox inside its open turn. If the proposed step - * is rejected, the claimed message ends here: it is neither discarded nor - * re-emitted as a user/message, and the turn closes without a step. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the claimed message. - * @param payload.turn - the owning turn. + * The driver claimed one item out of the inbox: a queued item at a turn + * boundary, or steering drained between steps. Fires after the item leaves + * its FIFO and before it becomes a durable message. + * @param agent - the agent whose inbox item was claimed. + * @param item - the exact claimed occurrence. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void +'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discarded` — emit +#### `agent/inbox/discard` — emit -One message was discarded from the live inbox. +Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. ```ts cordis-catalog /** - * One message was discarded from the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the discarded message. + * Pending inbox items were dropped without delivering them, so every + * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR + * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, + * emits this after `agent/cancel-requested` when applicable and before + * aborting the active work. Fires once per drop with every dropped item. + * @param agent - the agent whose inbox items were dropped. + * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/inserted` — emit +#### `agent/inbox/enqueue` — emit -One message entered the live inbox. +An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. ```ts cordis-catalog /** - * One message entered the live inbox. - * @param payload.agent - the agent whose inbox changed. - * @param payload.message - the inserted message. + * An item entered the queued or steering inbox. `placement` is the + * acceptance-time routing result; listeners must not reconstruct it from + * later agent or session state. + * @param agent - the owning agent. + * @param item - accepted occurrence, message, and resolved placement. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void +'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void ``` -Types: [Scoped](scope.md) · [UserMessage](session.md) +Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/pre-step` — waterfall +#### `agent/inbox/update` — emit -Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. +A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. ```ts cordis-catalog /** - * Reject a proposed step or replace the messages that enter it. Calling - * `next()` preserves the current messages. - * @param payload.agent - the agent proposing the step. - * @param payload.messages - messages removed from the inbox for this step. - * @param payload.turn - the turn that will own the step. - * @param payload.step - the step proposed by the loop. - * @param payload.signal - the current turn's cancellation signal. + * A still-pending queued item changed content. The item id, placement, and + * position remain stable while the event carries the replacement message. + * @param agent - the owning agent. + * @param item - the complete post-update occurrence. + * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. + * @mode emit + */ +'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void +``` + +Types: [Scoped](scope.md) + +Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) + + + +#### `agent/prompt-submit` — waterfall + +Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. + +```ts cordis-catalog +/** + * Allow, rewrite, or block one claimed prompt before it becomes a user + * message or opens a turn. Call `next()` for the unchanged default. The + * signal controls only this admission attempt; listeners may cooperate with + * it but must not retain it for a later attempt or turn. + * @param agent - the agent whose turn claimed the message. + * @param message - the frozen claimed message, including identity and source. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -1135,17 +867,17 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param payload.agent - the agent making the model call. - * @param payload.turn - the open turn number. - * @param payload.step - the step whose request this is. - * @param payload.signal - the current turn's explicit abort signal. + * @param agent - the agent making the model call. + * @param turn - the open turn number. + * @param step - the step whose request this is. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise +'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise ``` -Types: [Scoped](scope.md) +Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts) @@ -1153,25 +885,28 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle one failed model-request attempt before the loop retries or closes - * its step. A listener returns `{ kind: 'retry' }` without calling `next()` - * when it owns recovery, or calls `next()` to delegate. The default - * `undefined` leaves the failure terminal. - * @param payload.agent - the agent whose request failed. - * @param payload.turn - the turn containing the failed request. - * @param payload.step - the step containing the failed request attempt. - * @param payload.provider - the provider selected for the failed request. - * @param payload.failure - serializable facts normalized at the final adapter boundary. - * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. - * @param payload.signal - the turn abort signal. + * Handle a model-request failure after its failed step has closed but + * before the failed turn closes. A listener returns `{ kind: 'retry' }` + * without calling `next()` when it owns the error, or calls `next()` to + * delegate. The default `undefined` leaves the failure terminal. + * @param agent - the agent whose request failed. + * @param turn - the open turn number. + * @param step - the failed step number. + * @param error - the original model-request failure. + * @param failure - serializable facts normalized at the final adapter boundary. + * @param priorFailures - immutable failures that already authorized another + * retry turn in this consecutive sequence. + * @param retryPolicy - immutable policy of the adapter registration that served + * the failed request, or `undefined` if no final adapter served it. + * @param signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise +'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -1190,12 +925,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param payload.agent - the agent whose session lifecycle began. - * @param payload.source - why the session started (fresh startup, resume, …). + * @param agent - the agent whose session lifecycle began. + * @param source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void +'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void ``` Types: [Scoped](scope.md) @@ -1206,19 +941,18 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. +Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). A waking delivery enters - * `running` synchronously after reserving cancellation; `idle` means no - * driver remains scheduled or active. - * @param payload.agent - the agent whose status flipped. - * @param payload.status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). `send()` does not enter + * `running` synchronously; drive lifecycle from this event. + * @param agent - the agent whose status flipped. + * @param status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void +'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void ``` Types: [Scoped](scope.md) @@ -1229,7 +963,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. ```ts cordis-catalog /** @@ -1239,17 +973,14 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. The conclusion - * never short-circuits already-submitted next-step work: same-step - * `additionalContexts` or racing steering still runs, and the turn - * closes only when that inbox drains. - * @param payload.agent - the agent whose turn is at its stop boundary. - * @param payload.turn - the turn about to close. - * @param payload.signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. + * @param agent - the agent whose turn is at its stop boundary. + * @param turn - the turn about to close. + * @param signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void +'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void ``` Types: [Scoped](scope.md) @@ -1272,12 +1003,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param payload.sessionId - exact shared agent/session identity that failed startup. - * @param payload.error - persistence, setup, or publication failure. + * @param sessionId - exact shared agent/session identity that failed startup. + * @param error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void +'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void ``` -Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/filesystem.i18n.yaml b/docs/subsystems/filesystem.i18n.yaml index cadc2b92d8..e57b2aef45 100644 --- a/docs/subsystems/filesystem.i18n.yaml +++ b/docs/subsystems/filesystem.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md -filesystem.md: 1999399254fa5d72ab222a42a2fa8f4b890da50a -filesystem.zh.md: 044a1c5be91ed8bec5d0442cc6f7edfde6870785 +filesystem.md: 591f1b4d4b1177ccbe7c2280d2054da5d4be8b99 +filesystem.zh.md: d599e48c1fba2d564a544f628ca3475b781e5cd5 diff --git a/docs/subsystems/filesystem.md b/docs/subsystems/filesystem.md index 1999399254..591f1b4d4b 100644 --- a/docs/subsystems/filesystem.md +++ b/docs/subsystems/filesystem.md @@ -256,6 +256,10 @@ type FsErrorCode = `FS_NOT_DIRECTORY`, `FS_PERMISSION_DENIED`, and `FS_IO_ERROR` are used by directory listing to distinguish an existing non-directory target, a denied listing, and an unexpected backend I/O failure. `FS_SANDBOX_DENIED` is a POLICY refusal from a sandbox-enforcing backend (`dsh-fs-sandbox`) — the mode fence denied a write/edit — distinct from `FS_PERMISSION_DENIED` (the host kernel refusing). `FS_NOT_OBSERVED` means the policy plugin has no prior-observation record for this owner (or a `createIfAbsent` hit an existing file). `FS_STALE_VERSION` means the backend version no longer matches the observed one (or an edit hit a missing target). Freshness authorization has no partial/full distinction, so there is no `FS_PARTIAL_OBSERVATION`. +## No timeouts on file IO + +`read`/`write`/`edit` take **no** `timeoutMs`, and the provider seam arms no deadline — unlike bash and web (which consume [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md)) and the bash-backed `glob`/`grep` (whose declared `timeoutMs` is enforced by `@deepseek-ai/dsh-timeout-policy`): those are process-backed, where a deadline can really kill the work. A local syscall is best-effort-abortable at most — a timeout could not force an in-progress `fsync`/`rename` to stop, so a deadline here would be a knob that cannot deliver on its promise, and an implicit default in the exact place explicit-over-implicit forbids. Both reference agents (Claude Code, Codex) leave file IO untimed for the same reason; cancellation still propagates through the tool-execution signal for best-effort abort at syscall boundaries. + ## The service and the plugin `FileSystem` (`ctx.fs`, abstract) owns the provider primitives: `resolve`, `processPath`, `fileUrl`, `contains`, `stat`, `lstat`, `readText`, `streamText`, `listDir`, `writeText`, and `editText`. `dsh-fs-policy` registers **no service** — it is a plugin that adds policy through the `fs/*` event gate: it decides the write/edit intent waterfalls (supplying `createIfAbsent`/`replaceIfVersion`/`{ version }` or throwing `FS_NOT_OBSERVED`) and records on `fs/observed`. The executor is `dsh-tool-fs`: it reads/writes/edits through `ctx.fs`, dispatches the waterfalls, and emits the recording event. The generated [`ctx.fs` section](#ctxfs--filesystem-abstract-seam) below shows the exact signatures. diff --git a/docs/subsystems/filesystem.zh.md b/docs/subsystems/filesystem.zh.md index 044a1c5be9..d599e48c1f 100644 --- a/docs/subsystems/filesystem.zh.md +++ b/docs/subsystems/filesystem.zh.md @@ -256,6 +256,10 @@ type FsErrorCode = 目录列表使用 `FS_NOT_DIRECTORY`、`FS_PERMISSION_DENIED` 与 `FS_IO_ERROR` 区分已存在但并非目录的目标、被拒绝的列表操作和意外的后端 I/O 失败。`FS_SANDBOX_DENIED` 是强制执行沙箱的后端(`dsh-fs-sandbox`)所作的策略拒绝——模式边界拒绝了写入/编辑——与 `FS_PERMISSION_DENIED`(宿主内核拒绝)不同。`FS_NOT_OBSERVED` 表示策略插件没有此所有者的先前观察记录(或 `createIfAbsent` 遇到了现有文件)。`FS_STALE_VERSION` 表示后端版本不再与观察到的版本匹配(或编辑操作遇到缺失目标)。新鲜度授权没有部分/完整之分,因此不存在 `FS_PARTIAL_OBSERVATION`。 +## 文件 IO 不设超时 + +`read`/`write`/`edit` **不**接受 `timeoutMs`,提供方 seam 也不设置截止时间——不同于 bash 与 web(它们消费 [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md))以及 bash 支撑的 `glob`/`grep`(其声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):那些是进程支撑的,截止时间可以真正终止工作。本地系统调用至多是尽力中止——超时无法迫使进行中的 `fsync`/`rename` 停下,因此这里的截止时间会成为无法兑现承诺的旋钮,而且恰好落在"显式优于隐式"禁止隐式默认值的位置。两个参照 agent(Claude Code、Codex)出于同样原因不给文件 IO 计时;取消仍通过工具执行 signal 传播,在系统调用边界尽力中止。 + ## 服务与插件 `FileSystem`(`ctx.fs`,abstract)拥有提供方原语:`resolve`、`processPath`、`fileUrl`、`contains`、`stat`、`lstat`、`readText`、`streamText`、`listDir`、`writeText` 与 `editText`。`dsh-fs-policy` **不注册服务**——它是一个通过 `fs/*` 事件门禁添加策略的插件:对写入/编辑意图 waterfall 作出决策(提供 `createIfAbsent`/`replaceIfVersion`/`{ version }`,或抛出 `FS_NOT_OBSERVED`),并在 `fs/observed` 上记录。执行器是 `dsh-tool-fs`:它通过 `ctx.fs` 读取/写入/编辑,分发 waterfall,并 emit 记录事件。下方生成的 [`ctx.fs` 小节](#ctxfs--filesystem-abstract-seam) 展示确切的 `ctx.fs` 签名。 diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index bd1d833f51..f6ce932ad9 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md -llm-streaming.md: fe4a831660dd81b33a1cd7dc9a9d7026f3831991 -llm-streaming.zh.md: a10b696ec0887e139785fc7e95d8641e6496de79 +llm-streaming.md: b3f850f4d07ad9e36d0e0db71dd9e0498e468767 +llm-streaming.zh.md: bb31c14b9bd0453399d83968b1062f67819caca3 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index fe4a831660..b3f850f4d0 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -2,10 +2,82 @@ English | [中文](llm-streaming.zh.md) -The wire-level streaming vocabulary of [dsh-llm](../../packages/llm/llm). [core.md](core.md) introduces `StreamChunk`, `Message`, and `ContentBlock`; this page owns the full chunk protocol, the adapter contract every adapter must obey, and the shared assembler. +The conversation and streaming vocabulary of [`packages/llm`](../../packages/llm/README.md): the `Message`/`ContentBlock` shapes every request and durable history share, the fully-assembled model request, the raw `StreamChunk` protocol, the adapter contract every adapter must obey, and the shared assembler. The [core spine](core.md) holds and logs these values on every turn; this page declares them. Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + + +## Content blocks and messages + +A conversation is `Message`s; a message is an array of typed **content blocks**. The block union derives from `ContentBlockMap`. + +Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +```ts type-equiv +/** + * Merge-extensible content blocks keyed by `type`. New core blocks must land + * with adapter, UI, and compaction support. + */ +interface ContentBlockMap { + 'text': TextBlock + 'reasoning': ReasoningBlock + 'tool-call': ToolCallBlock + 'tool-result': ToolResultBlock +} +``` + +The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBlock` (thinking, distinct from visible text), `ToolCallBlock` (`id: CallId`, `name`, raw-JSON `arguments`), `ToolResultBlock` (`toolCallId`, nested `content: ContentBlock[]`, `isError?`). `ContentBlock = ContentBlockMap[ContentBlockType]`. The core set is limited to blocks every shipping path honors — multimodal content (images, audio, …) has no core block type; a feature that needs one adds it via the merge-extensible map together with the adapter/UI/compaction support that honors it. + +Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) + +A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages carry provider/model ownership and optional adapter-private replay metadata in their source: + +```ts type-equiv +/** Provider ownership and adapter-private replay data for an assistant message. */ +interface AssistantProvenance { + /** Provider route that produced the message. */ + provider: string + /** Provider model id that produced the message. */ + model: string + /** + * Lossless-JSON adapter state needed to replay the provider response. + * `LlmService` exposes it to a target adapter only when that adapter instance + * currently owns both this historical provider and the target provider. + */ + replayState?: unknown +} +``` + +```ts type-equiv +/** One immutable message representation shared by delivery, durable history, and model requests. */ +interface Message { + /** Stable identity preserved across every representation boundary. */ + readonly id: MessageId + /** Provider-neutral conversation role. */ + readonly role: 'system' | 'user' | 'assistant' + /** Exact model-facing blocks. */ + readonly content: ContentBlock[] + /** Required producer provenance. */ + readonly source: MessageSource +} +``` + +Where a message came from is itself a merge-extensible sum type: + +```ts type-equiv +/** + * Where a message (or injected content) came from. + * Merge-extensible sum type — plugins add their own `kind`s. + */ +interface MessageSourceMap { + user: { kind: 'user' } + plugin: { kind: 'plugin'; plugin: string } + model: ModelMessageSource + tool: ToolMessageSource +} +``` + ## `StreamChunk` — the raw protocol A streaming response interleaves several typed blocks (text, reasoning, multiple tool calls). `index` ties each delta to its block; `block-end` carries the fully-assembled `ContentBlock` so consumers don't have to re-assemble deltas themselves. It is a **closed** discriminated union — a `switch` over `type` ends with `assertNever`, so adding a variant breaks compilation at every consumer that must handle it. @@ -15,9 +87,8 @@ A streaming response interleaves several typed blocks (text, reasoning, multiple * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. An adapter implementation - * may throw, but `LlmService.stream()` normalizes that failure to a terminal - * `error` or `aborted` finish before exposing it to consumers. + * afterward; tool arguments remain raw JSON strings. Failures either throw or + * end with `error`/`aborted`, and consumers must handle both paths. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -65,10 +136,10 @@ Every adapter MUST obey these, and every consumer may rely on them: - **Provider stalls are bounded at the transport.** Both shipping remote adapters expose positive finite `streamIdleTimeoutMs` with a five-minute default. The watchdog arms only while iterator `next()` is outstanding, uses one stable signal for the whole request, maps its own expiry to `TIMEOUT`, and keeps an earlier caller abort as `ABORTED`. - **Context overflow has one canonical code.** Both DeepSeek adapters classify explicit provider detail through `isContextWindowExceededError()` and surface `CONTEXT_WINDOW_EXCEEDED`, whether the failure arrives as a thrown HTTP `LlmError` or an in-band finish error. Consumers route on the code, never provider text. - **An empty completion is a retryable error, not a silent success.** Both adapters map a terminal `stop` finish that carried no content blocks to `finish {kind:'error'}` with the canonical `EMPTY_RESPONSE` code, and `dsh-llm-retry` retries it by default; see [empty model responses are retryable](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md). -- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below), the `User-Agent` baseline. +- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below) - the `User-Agent` baseline - and prove it with a wire-level test (mock server asserting the received header, or the library's header hook for a library-backed adapter). - **Replay state is adapter-owned.** A successful `finish` may carry lossless-JSON state needed to reconstruct a native provider response. The loop stores it with the assembled assistant message. On a later request, `LlmService` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content and provenance without the private state. -Two independent implementations obey this contract: `dsh-llm-deepseek` uses direct fetch with SSE framing through `eventsource-parser`, while `dsh-llm-pi-ai` provides a generic multi-provider adapter through `@earendil-works/pi-ai`. Both carry cancellation and the idle watchdog to the provider request. +This contract is pinned down by two deliberately independent implementations: `dsh-llm-deepseek` (direct fetch, SSE framing via `eventsource-parser`) and `dsh-llm-pi-ai` (a generic multi-provider adapter through `@earendil-works/pi-ai`). The library-backed adapter exercises the finish-chunk error path, while transport-boundary tests prove each idle watchdog stops its actual request. ## `ResolvedRetryPolicy` @@ -142,9 +213,8 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index, except that max-token truncation drops - * tool calls that cannot be executed safely; an open block assembles from - * its accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index; an open block assembles from its + * accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -162,6 +232,263 @@ declare class BlockAssembler { } ``` + + +## The model request + +One model call is a fully-assembled `GenerateOptions`. The adapter answers with a raw [`StreamChunk`](#streamchunk--the-raw-protocol) stream; the consumer assembles it with [`BlockAssembler`](#blockassembler). + +Source: [`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +Provider and model discovery uses small provider-neutral descriptors. A model catalog is advisory: routing still keys on a registered provider, and an adapter may accept unlisted model ids. + +Registering an adapter returns a handle: the disposer, plus the atomic route replacement a plugin whose route set is user-configurable needs. + +```ts type-equiv +/** + * What {@link LlmService.registerAdapter} returns: the disposer, plus an + * atomic route replacement for the same adapter instance. + */ +interface AdapterRegistrationHandle { + /** Release every route this registration currently holds. */ + (): void + /** + * Replace this registration's routes with `providers`, keeping the same + * adapter instance. The candidate set is validated in full first — a + * conflict with another adapter, an invalid name, or bad provider metadata + * throws and leaves the current routes untouched — and the swap itself is + * one synchronous section, so no request can observe a gap. An empty array + * is legal here (a settings section that emptied holds zero routes while + * staying registered), unlike an empty initial registration. + * + * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration + * has been released: its routes are gone and its disposer has already run, + * so anything registered afterwards would have no owner left to release it. + * @param providers - the complete next route set for this registration. + */ + replace(providers: string[]): void +} +``` + +```ts type-equiv +/** Display metadata for one registered provider route. */ +interface LlmProviderInfo { + /** Provider route key used by {@link GenerateOptions.provider}. */ + id: string + /** Human-readable provider name for selectors and diagnostics. */ + name: string +} +``` + +Adapter plugins additionally declare which routes *could* run through `registerConfigurableProviders()`, addressing each one's user-settings section, so configuration surfaces can offer dormant providers before any route registers. + +```ts type-equiv +/** + * One provider route an adapter plugin can activate through configuration, + * whether or not the route is currently registered. Configuration surfaces + * merge this directory with `listProviders()` to offer every configurable + * provider alongside its live/dormant state. + */ +interface LlmConfigurableProvider { + /** Provider route key this entry activates when configured. */ + provider: string + /** Human-readable provider name for configuration surfaces. */ + displayName: string + /** User-settings namespace whose section configures this provider. */ + settingsNs: string + /** + * Path from that namespace's section root to this provider's profile + * object; empty when the whole section is the profile. + */ + settingsPath: readonly string[] +} +``` + +```ts type-equiv +/** One adapter-discovered model; catalog membership is advisory, not request validation. */ +interface LlmModelInfo { + /** Provider route that owns this model entry. */ + provider: string + /** Model id passed to {@link GenerateOptions.model}. */ + id: string + /** Human-readable model name for selectors. */ + name: string + /** Optional user-facing distinction from otherwise similar models. */ + description?: string +} +``` + +Correctness-sensitive metadata is resolved separately from the advisory catalog and is owned by the adapter serving the exact route. Context capacity, adapter call defaults, and reasoning choices share one exact-model result so consumers do not repeat authoritative model resolution. + +```ts type-equiv +/** Provider-owned context capacity for one exact provider/model route. */ +interface LlmModelContext { + /** Maximum combined request and response context in tokens. */ + contextWindow: number +} +``` + +Reasoning effort is another exact-route capability. The core brands identifiers but does not enumerate their values; each adapter owns the ordered set, display names, and optional deployment default. + +```ts type-equiv +/** Adapter-owned identifier for one model's selectable reasoning effort. */ +type ReasoningEffortId = Branded<'ReasoningEffortId'> +``` + +```ts type-equiv +/** Display metadata for one adapter-owned reasoning effort. */ +interface LlmReasoningEffortInfo { + /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ + id: ReasoningEffortId + /** Human-readable effort name for selectors and diagnostics. */ + name: string + /** Optional user-facing distinction from otherwise similar efforts. */ + description?: string +} +``` + +```ts type-equiv +/** Selectable reasoning efforts for one exact provider/model route. */ +interface LlmModelReasoningInfo { + /** Supported efforts in adapter-preferred display order. */ + efforts: readonly LlmReasoningEffortInfo[] + /** + * Adapter-configured default materialized into requests when callers omit + * an effort. Absence preserves the provider's own default. + */ + defaultEffort?: ReasoningEffortId +} +``` + +```ts type-equiv +/** Exact-route model metadata resolved by its owning adapter. */ +interface LlmResolvedModelInfo extends LlmModelInfo { + /** Provider-owned context capacity when known. */ + context?: LlmModelContext + /** Adapter-configured per-request output cap materialized when callers omit one. */ + defaultMaxTokens?: number + /** Adapter-owned selectable reasoning levels when exposed. */ + reasoning?: LlmModelReasoningInfo +} +``` + +```ts type-equiv +/** A single model request, fully assembled. */ +interface GenerateOptions { + /** Registered provider route selecting the adapter instance. */ + provider: string + model: string + /** Adapter-owned reasoning effort selected for this exact model. */ + reasoningEffort?: ReasoningEffortId + /** + * Ordered conversation messages, exactly as the provider sees them (after + * the `system` slot). A loop-built request assembles them as + * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. + */ + messages: Message[] + /** System prompt text (adapters map to the provider's system slot). */ + system?: string + /** Tool schemas (adapters map to the provider's `tools` field). */ + tools?: ToolSchema[] + temperature?: number + maxTokens?: number + /** + * Stop sequences: generation halts as soon as the model produces any one of + * these strings (adapters map to the provider's stop field, e.g. OpenAI + * `stop`). The stop string itself is not included in the output. + */ + stop?: string[] + signal?: AbortSignal + /** + * Session identity stamped by the loop for listener routing. Adapters ignore + * it; replay uses it to keep concurrent parent and child cursors independent. + */ + sessionId?: Branded<'SessionId'> + /** + * Provider-neutral classification for an auxiliary model call. Adapters may + * map the purpose to model-hidden transport metadata or purpose-specific + * generation policy. Ordinary conversation requests leave it unset. + */ + purpose?: 'compaction' | 'session-title' +} +``` + +Why a model response stopped is a merge-extensible reason. Terminal provider failures carry the streaming contract's [`LlmFailure`](#llmfailure): + +```ts type-equiv +/** + * Why a model response stopped. + * Merge-extensible so adapters can surface provider-specific reasons. + */ +interface FinishReasonMap { + 'stop': { kind: 'stop' } + 'tool-calls': { kind: 'tool-calls' } + 'max-tokens': { kind: 'max-tokens' } + 'aborted': { kind: 'aborted'; failure: LlmFailure } + 'error': { kind: 'error'; failure: LlmFailure } +} +``` + +`FinishReason = FinishReasonMap[keyof FinishReasonMap]`. `TokenUsage` (per-call accounting with disjoint cache fields) is detailed [below](#tokenusage). + +`GenerateOptions.tools` carries `ToolSchema` — the JSON-schema description of a tool, as sent to the model. It is declared in dsh-llm (not dsh-tools) precisely because it is part of the request the loop assembles every step: + +```ts type-equiv +/** + * JSON-schema description of a tool, as sent to the model. + * + * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; + * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import + * it from this package. + */ +interface ToolSchema { + name: string + description: string + /** JSON Schema object for the arguments. */ + parameters: Record +} +``` + +The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition` that produces it (schema + `execute`) is on [tools.md](tools.md). + +### The request envelope: `LlmCallConfig` and the logged header + +The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md). + +`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus provenance under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests. + +On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request. + +FIXME(call-config-shape): revisit which remaining fields are genuinely epoch-level for cache purposes (`model` and the model-owned reasoning effort are explicit; the sampling scalars sit here out of caution). + +```ts type-equiv +/** + * Provider, model, reasoning effort, and sampling scalars of one conversation's + * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; + * the loop builds requests from the logged header rather than accepting these + * per call. + */ +interface LlmCallConfig { + provider: string + model: string + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + +```ts type-equiv +/** + * Effective config fields supplied by exact-model adapter resolution rather + * than by the caller's request proposal. + */ +interface LlmCallConfigAdapterDefaults { + reasoningEffort?: true + maxTokens?: true +} +``` + ## The seam `LlmAdapter` is the provider seam: subclass, implement `stream()`, and register one adapter instance with `ctx.llm.registerAdapter(providers, adapter)`. `GenerateOptions.provider` selects the registered adapter; `GenerateOptions.model` is passed to that adapter and need not be registered at lifecycle start. Duplicate provider routes fail atomically. Optional `providerRetryPolicy()` is captured per route with normal defaults, while `providerInfo()` and asynchronous `listModels()` feed `LlmService.listProviders()` / `listModels()` with detached selector metadata. That catalog is advisory rather than a request whitelist: the adapter remains authoritative and may accept unlisted model ids. One asynchronous `resolveModel()` query returns exact model identity plus optional correctness-sensitive context capacity, an adapter-configured `defaultMaxTokens`, and ordered model-owned reasoning ids with an optional deployment default; absent fields mean unavailable metadata or provider-owned behavior, not invalid catalog membership. The resolver receives optional cancellation and must settle promptly after abort. `LlmService.resolveModelInfo()` validates and detaches the aggregate. At the final adapter boundary, `resolveCallConfig()` materializes the output default only when `maxTokens` is absent and validates and materializes reasoning, so direct calls cannot bypass either configured behavior; direct dispatch captures one registration before awaiting that resolution. The agent loop instead uses `prepareCall()` to keep the same registration across model resolution, durable header logging, and dispatch, retain detached context metadata from that exact lookup, and report which config fields the adapter defaulted. Adapter lookup happens at the terminal continuation of the `llm/stream` waterfall, so a listener may short-circuit the call or route a mutable one-shot request before lookup. AgentLoop observes a request attempt once the outer waterfall returns a stream handle; that limited boundary does not prove a lazy terminal adapter was constructed or began provider I/O. The `block-start` / `block-end` `index` correlation and the assembler together mean an adapter only has to emit well-formed chunks — block reassembly is not each adapter's problem. The consumer surface (`ctx.llm.stream()`) and the `llm/stream` waterfall are described in [architecture.md § Content blocks and streaming](../architecture.md#content-blocks-and-streaming-dsh-llm). @@ -171,8 +498,6 @@ declare class BlockAssembler { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig - /** Immutable retry policy captured with the adapter registration. */ - readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -239,22 +564,7 @@ declare abstract class LlmAdapter { } ``` -`ContentBlockType` (the key set the `index`-correlated blocks carry) derives from `ContentBlockMap`: - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -See [core.md § Content blocks and messages](core.md#content-blocks-and-messages) for the block interfaces. +`ContentBlockType` (the key set the `index`-correlated blocks carry) derives from [`ContentBlockMap`](#content-blocks-and-messages) above. @@ -293,9 +603,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns a handle that withdraws all of them, and can atomically replace them. + * @returns the disposer that withdraws all of them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void /** * List every declared configurable provider, registered or dormant. @@ -303,29 +613,6 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): Dire */ listConfigurableProviders(): LlmConfigurableProvider[] -/** - * Offer to interrogate provider endpoints on behalf of the settings - * namespace this plugin owns. The namespace is the key because that is what - * a configuration surface already holds from the configurable-provider - * directory, and because a provider being *added* has no route to name yet. - * Disposed with the fiber. - * @param settingsNs - the namespace whose profiles this discovery serves. - * @param discover - interrogates one endpoint; must honor `request.signal`. - * @returns the disposer that withdraws the offer. - */ -registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void - -/** - * Interrogate one provider endpoint for the models it advertises. The - * request describes a draft, not a stored route, so nothing here reads or - * writes settings or credentials — the caller owns both, and the reply is - * candidate metadata a surface may offer for adoption. - * @param settingsNs - namespace whose registered discovery serves this draft. - * @param request - the endpoint, protocol, and one-shot credential to use. - * @returns the advertised models, deduplicated in endpoint order. - */ -async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise - /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -375,22 +662,22 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Replay state is - * retained only when the same adapter instance owns its historical provider - * and the target provider. Final adapter selection remains fixed through - * asynchronous exact-model resolution and dispatch. Adapter selection, - * dispatch, and iteration failures become terminal `error` or `aborted` - * finish chunks; middleware, nested-call, cleanup, and consumer failures - * remain thrown. + * Stream one model call as raw chunks (token-level deltas). Throws + * `LlmError` with code `NO_ADAPTER` if no adapter is registered for + * `options.provider`. Replay state is retained only when the same adapter + * instance owns its historical provider and the target provider. Final + * adapter selection remains fixed through asynchronous exact-model resolution + * and dispatch. Selection, dispatch, and iteration failures retain their + * original Error identity and are tagged in a call-local scope for narrow + * agent-loop request recovery; middleware and nested-call failures remain + * untagged for the outer call. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) - -Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) @@ -415,7 +702,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) @@ -439,7 +726,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Types: [GenerateOptions](core.md) - -Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index a10b696ec0..bb31c14b9b 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -2,10 +2,84 @@ [English](llm-streaming.md) | 中文 -[dsh-llm](../../packages/llm/llm) 的协议格式(wire format)级流式输出词汇。[core.md](core.md) 介绍了 `StreamChunk`、`Message` 与 `ContentBlock`;本页拥有完整的分片协议、每个适配器必须遵守的适配器契约(adapter contract),以及共享的 assembler。 +[`packages/llm`](../../packages/llm/README.md) 的对话与流式输出词汇:每个请求与持久历史共享的 `Message`/`ContentBlock` 形状、完整组装的模型请求、原始 `StreamChunk` 协议、每个适配器必须遵守的适配器契约(adapter contract),以及共享的 assembler。[核心主干](core.md)在每个轮次持有并记录这些值;本页声明它们。 源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + + +## 内容块与消息 + +一段对话由 `Message` 组成;一条消息是一个类型化**内容块**的数组。块的联合类型从 `ContentBlockMap` 派生。 + +源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +```ts type-equiv +/** + * Merge-extensible content blocks keyed by `type`. New core blocks must land + * with adapter, UI, and compaction support. + */ +interface ContentBlockMap { + 'text': TextBlock + 'reasoning': ReasoningBlock + 'tool-call': ToolCallBlock + 'tool-result': ToolResultBlock +} +``` + +各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`)、`ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。核心集仅限于每条交付路径都尊重的块——多模态内容(图像、音频等)没有核心块类型;需要的功能通过可合并扩展的 map 添加,同时提供适配器/UI/压缩支持。 + +源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts) + +`Message` 是一个带标识且不可变的角色/来源/内容值。模型产生的 assistant 消息会在其来源中携带提供方/模型所有权与可选的适配器私有回放元数据: + +```ts type-equiv +/** Provider ownership and adapter-private replay data for an assistant message. */ +interface AssistantProvenance { + /** Provider route that produced the message. */ + provider: string + /** Provider model id that produced the message. */ + model: string + /** + * Lossless-JSON adapter state needed to replay the provider response. + * `LlmService` exposes it to a target adapter only when that adapter instance + * currently owns both this historical provider and the target provider. + */ + replayState?: unknown +} +``` + +```ts type-equiv +/** One immutable message representation shared by delivery, durable history, and model requests. */ +interface Message { + /** Stable identity preserved across every representation boundary. */ + readonly id: MessageId + /** Provider-neutral conversation role. */ + readonly role: 'system' | 'user' | 'assistant' + /** Exact model-facing blocks. */ + readonly content: ContentBlock[] + /** Required producer provenance. */ + readonly source: MessageSource +} +``` + +消息来源本身也是一个可合并扩展的和类型: + +```ts type-equiv +/** + * Where a message (or injected content) came from. + * Merge-extensible sum type — plugins add their own `kind`s. + */ +interface MessageSourceMap { + user: { kind: 'user' } + plugin: { kind: 'plugin'; plugin: string } + model: ModelMessageSource + tool: ToolMessageSource +} +``` + + + ## `StreamChunk`:原始协议 一个流式响应交错包含多种类型的块(文本、推理(reasoning)、多个工具调用)。`index` 将每个 delta 关联到其所属块;`block-end` 携带完整组装好的 `ContentBlock`,消费方无需自行重新组装 delta。这是一个**封闭的**可辨识联合类型:对 `type` 的 `switch` 以 `assertNever` 结尾,因此新增变体会在每个必须处理它的消费方处触发编译错误。 @@ -15,9 +89,8 @@ * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. An adapter implementation - * may throw, but `LlmService.stream()` normalizes that failure to a terminal - * `error` or `aborted` finish before exposing it to consumers. + * afterward; tool arguments remain raw JSON strings. Failures either throw or + * end with `error`/`aborted`, and consumers must handle both paths. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -34,6 +107,8 @@ type StreamChunk = } ``` + + ## `LlmFailure` 每个抛出的失败或最终适配器的带内失败都会规范化为一种可序列化、提供方无关的 payload。`providerRetryAfterMs` 是经校验、由提供方请求的正数延迟,而不是重试决策;`ProviderRequestId` 是用于诊断的不透明品牌字符串。 @@ -65,10 +140,10 @@ interface LlmFailure { - **提供方停顿在传输层受到时限约束。** 两个已交付的远程适配器都暴露正数且有限的 `streamIdleTimeoutMs`,默认五分钟。watchdog 只在 iterator `next()` 尚未完成时启动,整个请求使用同一个稳定 signal,把自身到期映射为 `TIMEOUT`,并把更早发生的调用方中止保留为 `ABORTED`。 - **上下文溢出只有一个规范 code。** 两个 DeepSeek 适配器都通过 `isContextWindowExceededError()` 对提供方的显式细节分类并暴露 `CONTEXT_WINDOW_EXCEEDED`,无论失败以抛出的 HTTP `LlmError` 还是带内 finish error 到达。消费方按 code 路由,绝不依赖提供方文本。 - **空 completion 是可重试错误,而不是静默的成功结果。** 两个适配器都把没有携带任何内容块的终止性 `stop` 结束映射为携带规范 `EMPTY_RESPONSE` code 的 `finish {kind:'error'}`,`dsh-llm-retry` 默认会重试它;详见[空模型响应可重试](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md)。 -- **每个提供方 HTTP 请求都携带应用归属头。** 适配器发送下文的 `attributionHeaders()`,即 `User-Agent` 基线。 +- **每个提供方 HTTP 请求都携带应用归属头。** 适配器发送 `attributionHeaders()`(见下文)作为 `User-Agent` 基线,并通过协议级测试加以证明(mock 服务器断言收到的 header,或对基于库的适配器使用库的 header 钩子)。 - **回放状态归适配器所有。** 成功的 `finish` 可以携带重建提供方原生响应所需的无损 JSON 状态。循环会将其与组装后的 assistant 消息一起存储。后续请求中,仅当历史提供方与目标提供方当前注册到完全相同的适配器实例时,`LlmService` 才会传递该状态。该适配器负责校验状态并拥有所有跨模型或跨提供方转换;其他适配器只会收到提供方无关的内容与 provenance,不会收到私有状态。 -两个彼此独立的实现遵循该契约:`dsh-llm-deepseek` 使用直接 fetch,并通过 `eventsource-parser` 进行 SSE(Server-Sent Events)分帧;`dsh-llm-pi-ai` 则通过 `@earendil-works/pi-ai` 提供通用多提供方适配器。两者都会把取消与空闲 watchdog 传递至提供方请求。 +该契约由两个有意保持独立的实现锁定:`dsh-llm-deepseek`(直接 fetch,SSE(Server-Sent Events)分帧经由 `eventsource-parser`)和 `dsh-llm-pi-ai`(通过 `@earendil-works/pi-ai` 实现的通用多提供方适配器)。基于库的适配器覆盖 finish 分片错误路径,而传输边界测试证明每个空闲 watchdog 都会停止其实际请求。 ## `ResolvedRetryPolicy` @@ -76,7 +151,7 @@ interface LlmFailure { ## `AppIdentity`:应用归属 -每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该契约有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包 manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。 +每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该契约有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包(package) manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。 ```ts type-equiv /** @@ -96,6 +171,8 @@ interface AppIdentity { } ``` + + ## `TokenUsage` 逐调用 token 记账。各计数**互不重叠**:`inputTokens` 只包含未缓存输入;缓存输入单独报告,计费输入是三者之和。若提供方把缓存命中折入单一提示词总数(如 DeepSeek 的 `prompt_tokens`),适配器会再将其扣除。`reasoningTokens` 存在时只是信息性细节,已经包含在 `outputTokens` 中;汇总时不得重复相加。 @@ -118,6 +195,8 @@ interface TokenUsage { } ``` + + ## `BlockAssembler` `BlockAssembler`([`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts))是唯一的共享实现,负责把 `StreamChunk` 流折叠回 `ContentBlock`、usage、结束原因与回放状态。循环在记录原始分片的同时,把同一批分片送入 assembler,再将组装后的 assistant 内容连同其提供方/模型 provenance 一起存储。需要组装结果、又不想重新实现 fold 的消费方使用它。 @@ -142,9 +221,8 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index, except that max-token truncation drops - * tool calls that cannot be executed safely; an open block assembles from - * its accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index; an open block assembles from its + * accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -162,6 +240,263 @@ declare class BlockAssembler { } ``` + + +## 模型请求 + +一次模型调用是一个完全组装好的 `GenerateOptions`。适配器以原始 [`StreamChunk`](#streamchunk--the-raw-protocol) 流作答;消费方用 [`BlockAssembler`](#blockassembler) 组装它。 + +源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts) + +提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。 + +注册适配器会返回一个句柄:既是释放器,也带有原子的路由替换——路由集合由用户配置决定的插件正需要它。 + +```ts type-equiv +/** + * What {@link LlmService.registerAdapter} returns: the disposer, plus an + * atomic route replacement for the same adapter instance. + */ +interface AdapterRegistrationHandle { + /** Release every route this registration currently holds. */ + (): void + /** + * Replace this registration's routes with `providers`, keeping the same + * adapter instance. The candidate set is validated in full first — a + * conflict with another adapter, an invalid name, or bad provider metadata + * throws and leaves the current routes untouched — and the swap itself is + * one synchronous section, so no request can observe a gap. An empty array + * is legal here (a settings section that emptied holds zero routes while + * staying registered), unlike an empty initial registration. + * + * Throws `LlmError` with code `REGISTRATION_DISPOSED` once the registration + * has been released: its routes are gone and its disposer has already run, + * so anything registered afterwards would have no owner left to release it. + * @param providers - the complete next route set for this registration. + */ + replace(providers: string[]): void +} +``` + +```ts type-equiv +/** Display metadata for one registered provider route. */ +interface LlmProviderInfo { + /** Provider route key used by {@link GenerateOptions.provider}. */ + id: string + /** Human-readable provider name for selectors and diagnostics. */ + name: string +} +``` + +适配器插件还会通过 `registerConfigurableProviders()` 声明哪些路由*可以*运行,并指明每条路由的用户设置分节,使配置界面能在任何路由注册之前就呈现休眠的提供方。 + +```ts type-equiv +/** + * One provider route an adapter plugin can activate through configuration, + * whether or not the route is currently registered. Configuration surfaces + * merge this directory with `listProviders()` to offer every configurable + * provider alongside its live/dormant state. + */ +interface LlmConfigurableProvider { + /** Provider route key this entry activates when configured. */ + provider: string + /** Human-readable provider name for configuration surfaces. */ + displayName: string + /** User-settings namespace whose section configures this provider. */ + settingsNs: string + /** + * Path from that namespace's section root to this provider's profile + * object; empty when the whole section is the profile. + */ + settingsPath: readonly string[] +} +``` + +```ts type-equiv +/** One adapter-discovered model; catalog membership is advisory, not request validation. */ +interface LlmModelInfo { + /** Provider route that owns this model entry. */ + provider: string + /** Model id passed to {@link GenerateOptions.model}. */ + id: string + /** Human-readable model name for selectors. */ + name: string + /** Optional user-facing distinction from otherwise similar models. */ + description?: string +} +``` + +对正确性敏感的元数据与参考目录分开解析,并归服务该确切路由的适配器所有。上下文容量、适配器调用默认值和推理选项共用同一个确切模型结果,消费方因而无需重复执行权威模型解析。 + +```ts type-equiv +/** Provider-owned context capacity for one exact provider/model route. */ +interface LlmModelContext { + /** Maximum combined request and response context in tokens. */ + contextWindow: number +} +``` + +推理强度是另一项针对确切路由的能力。核心为标识符添加品牌类型,但不枚举其值;有序集合、展示名称和可选的部署默认值均由各适配器持有。 + +```ts type-equiv +/** Adapter-owned identifier for one model's selectable reasoning effort. */ +type ReasoningEffortId = Branded<'ReasoningEffortId'> +``` + +```ts type-equiv +/** Display metadata for one adapter-owned reasoning effort. */ +interface LlmReasoningEffortInfo { + /** Opaque stable value accepted by {@link GenerateOptions.reasoningEffort}. */ + id: ReasoningEffortId + /** Human-readable effort name for selectors and diagnostics. */ + name: string + /** Optional user-facing distinction from otherwise similar efforts. */ + description?: string +} +``` + +```ts type-equiv +/** Selectable reasoning efforts for one exact provider/model route. */ +interface LlmModelReasoningInfo { + /** Supported efforts in adapter-preferred display order. */ + efforts: readonly LlmReasoningEffortInfo[] + /** + * Adapter-configured default materialized into requests when callers omit + * an effort. Absence preserves the provider's own default. + */ + defaultEffort?: ReasoningEffortId +} +``` + +```ts type-equiv +/** Exact-route model metadata resolved by its owning adapter. */ +interface LlmResolvedModelInfo extends LlmModelInfo { + /** Provider-owned context capacity when known. */ + context?: LlmModelContext + /** Adapter-configured per-request output cap materialized when callers omit one. */ + defaultMaxTokens?: number + /** Adapter-owned selectable reasoning levels when exposed. */ + reasoning?: LlmModelReasoningInfo +} +``` + +```ts type-equiv +/** A single model request, fully assembled. */ +interface GenerateOptions { + /** Registered provider route selecting the adapter instance. */ + provider: string + model: string + /** Adapter-owned reasoning effort selected for this exact model. */ + reasoningEffort?: ReasoningEffortId + /** + * Ordered conversation messages, exactly as the provider sees them (after + * the `system` slot). A loop-built request assembles them as + * the derived history (dsh-agent-loop); a hand-built one-shot passes any list. + */ + messages: Message[] + /** System prompt text (adapters map to the provider's system slot). */ + system?: string + /** Tool schemas (adapters map to the provider's `tools` field). */ + tools?: ToolSchema[] + temperature?: number + maxTokens?: number + /** + * Stop sequences: generation halts as soon as the model produces any one of + * these strings (adapters map to the provider's stop field, e.g. OpenAI + * `stop`). The stop string itself is not included in the output. + */ + stop?: string[] + signal?: AbortSignal + /** + * Session identity stamped by the loop for listener routing. Adapters ignore + * it; replay uses it to keep concurrent parent and child cursors independent. + */ + sessionId?: Branded<'SessionId'> + /** + * Provider-neutral classification for an auxiliary model call. Adapters may + * map the purpose to model-hidden transport metadata or purpose-specific + * generation policy. Ordinary conversation requests leave it unset. + */ + purpose?: 'compaction' | 'session-title' +} +``` + +模型响应为何停止由可合并扩展的原因表示。提供方终态失败携带流式契约的 [`LlmFailure`](#llmfailure): + +```ts type-equiv +/** + * Why a model response stopped. + * Merge-extensible so adapters can surface provider-specific reasons. + */ +interface FinishReasonMap { + 'stop': { kind: 'stop' } + 'tool-calls': { kind: 'tool-calls' } + 'max-tokens': { kind: 'max-tokens' } + 'aborted': { kind: 'aborted'; failure: LlmFailure } + 'error': { kind: 'error'; failure: LlmFailure } +} +``` + +`FinishReason = FinishReasonMap[keyof FinishReasonMap]`。`TokenUsage`(逐调用计量,含不相交的缓存字段)详见[下文](#tokenusage)。 + +`GenerateOptions.tools` 携带 `ToolSchema`——工具的 JSON Schema 描述,发送给模型。它声明在 dsh-llm(而非 dsh-tools)中,正是因为它是循环每一步组装请求的一部分: + +```ts type-equiv +/** + * JSON-schema description of a tool, as sent to the model. + * + * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions}; + * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import + * it from this package. + */ +interface ToolSchema { + name: string + description: string + /** JSON Schema object for the arguments. */ + parameters: Record +} +``` + +面向模型的 `ToolSchema` 是协议格式;产出它的已注册 `ToolDefinition`(schema + `execute`)在 [tools.md](tools.md) 中。 + +### 请求信封:`LlmCallConfig` 与记录的 header + +循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。 + +`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置及其来源。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。 + +在协议格式上,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 `user/message`,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。 + +FIXME(call-config-shape):重新审视其余哪些字段出于缓存目的确实属于 epoch 层级(`model` 和模型持有的推理强度已明确属于;采样标量目前出于谨慎保留在此)。 + +```ts type-equiv +/** + * Provider, model, reasoning effort, and sampling scalars of one conversation's + * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field; + * the loop builds requests from the logged header rather than accepting these + * per call. + */ +interface LlmCallConfig { + provider: string + model: string + reasoningEffort?: ReasoningEffortId + temperature?: number + maxTokens?: number + stop?: string[] +} +``` + +```ts type-equiv +/** + * Effective config fields supplied by exact-model adapter resolution rather + * than by the caller's request proposal. + */ +interface LlmCallConfigAdapterDefaults { + reasoningEffort?: true + maxTokens?: true +} +``` + ## seam `LlmAdapter` 是提供方 seam:创建子类、实现 `stream()`,再用 `ctx.llm.registerAdapter(providers, adapter)` 注册一个适配器实例。`GenerateOptions.provider` 选择已注册适配器;`GenerateOptions.model` 会传给该适配器,无需在生命周期启动时注册。重复提供方路由会原子失败。可选的 `providerRetryPolicy()` 会按路由捕获并填入 normal 默认值,`providerInfo()` 与异步 `listModels()` 方法则为 `LlmService.listProviders()` / `listModels()` 提供分离的 selector 元数据。该目录仅供参考,不是请求白名单:适配器仍是权威,并可接受未列出的模型 id。单次异步 `resolveModel()` 查询返回确切模型身份,以及可选的对正确性敏感的上下文容量、适配器配置的 `defaultMaxTokens`、由模型持有的有序推理强度 ID 和部署默认值;字段缺失表示元数据不可用或保留提供方持有的行为,而不表示目录成员关系无效。解析器会接收可选的取消信号,并且必须在信号中止后迅速完成结算。`LlmService.resolveModelInfo()` 会校验聚合结果并返回分离值。在最终适配器边界,`resolveCallConfig()` 仅在 `maxTokens` 缺失时填入输出默认值,并校验和填入推理强度,因此直接调用也无法绕过任何一项已配置行为;直接分派会在等待解析前捕获一项适配器注册。agent loop 则使用 `prepareCall()`,使模型解析、请求头持久记录和分派全程使用同一项注册,保留来自同一次查询的分离上下文元数据,并报告适配器填入的配置字段。适配器查找发生在 `llm/stream` waterfall(瀑布式事件)的终端 continuation,因此 listener 可以在查找前短路调用,或路由一个可变的一次性请求。AgentLoop 在外层 waterfall 返回流句柄时观察到一次请求尝试;这个有限边界不能证明惰性终端适配器已构造完成或开始提供方 I/O。`block-start` / `block-end` 的 `index` 关联与 assembler 共同意味着适配器只需 emit 格式正确的分片——块重组不是每个适配器各自的问题。消费方 surface(`ctx.llm.stream()`)与 `llm/stream` waterfall 见 [architecture.md § 内容块与流式传输](../architecture.md#content-blocks-and-streaming-dsh-llm)。 @@ -171,8 +506,6 @@ declare class BlockAssembler { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig - /** Immutable retry policy captured with the adapter registration. */ - readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -239,22 +572,7 @@ declare abstract class LlmAdapter { } ``` -`ContentBlockType`(`index` 关联块所携带的键集合)派生自 `ContentBlockMap`: - -```ts type-equiv -/** - * Merge-extensible content blocks keyed by `type`. New core blocks must land - * with adapter, UI, and compaction support. - */ -interface ContentBlockMap { - 'text': TextBlock - 'reasoning': ReasoningBlock - 'tool-call': ToolCallBlock - 'tool-result': ToolResultBlock -} -``` - -块接口详见 [core.md § Content blocks and messages](core.md#content-blocks-and-messages)。 +`ContentBlockType`(带 `index` 关联的块所携带的键集合)从上文的 [`ContentBlockMap`](#content-blocks-and-messages) 派生。 @@ -293,9 +611,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns a handle that withdraws all of them, and can atomically replace them. + * @returns the disposer that withdraws all of them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void /** * List every declared configurable provider, registered or dormant. @@ -303,29 +621,6 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): Dire */ listConfigurableProviders(): LlmConfigurableProvider[] -/** - * Offer to interrogate provider endpoints on behalf of the settings - * namespace this plugin owns. The namespace is the key because that is what - * a configuration surface already holds from the configurable-provider - * directory, and because a provider being *added* has no route to name yet. - * Disposed with the fiber. - * @param settingsNs - the namespace whose profiles this discovery serves. - * @param discover - interrogates one endpoint; must honor `request.signal`. - * @returns the disposer that withdraws the offer. - */ -registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void - -/** - * Interrogate one provider endpoint for the models it advertises. The - * request describes a draft, not a stored route, so nothing here reads or - * writes settings or credentials — the caller owns both, and the reply is - * candidate metadata a surface may offer for adoption. - * @param settingsNs - namespace whose registered discovery serves this draft. - * @param request - the endpoint, protocol, and one-shot credential to use. - * @returns the advertised models, deduplicated in endpoint order. - */ -async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise - /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -375,22 +670,22 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Replay state is - * retained only when the same adapter instance owns its historical provider - * and the target provider. Final adapter selection remains fixed through - * asynchronous exact-model resolution and dispatch. Adapter selection, - * dispatch, and iteration failures become terminal `error` or `aborted` - * finish chunks; middleware, nested-call, cleanup, and consumer failures - * remain thrown. + * Stream one model call as raw chunks (token-level deltas). Throws + * `LlmError` with code `NO_ADAPTER` if no adapter is registered for + * `options.provider`. Replay state is retained only when the same adapter + * instance owns its historical provider and the target provider. Final + * adapter selection remains fixed through asynchronous exact-model resolution + * and dispatch. Selection, dispatch, and iteration failures retain their + * original Error identity and are tagged in a call-local scope for narrow + * agent-loop request recovery; middleware and nested-call failures remain + * untagged for the outer call. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Types: [AdapterRegistrationHandle](core.md) · [DirectoryRegistrationHandle](core.md) · [GenerateOptions](core.md) · [LlmCallConfig](core.md) · [LlmConfigurableProvider](core.md) · [LlmDiscoveredModel](core.md) · [LlmModelDiscoveryRequest](core.md) · [LlmModelInfo](core.md) · [LlmProviderInfo](core.md) · [LlmResolvedModelInfo](core.md) - -Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) @@ -415,7 +710,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) @@ -439,7 +734,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Types: [GenerateOptions](core.md) - -Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/session-reference.i18n.yaml b/docs/subsystems/session-reference.i18n.yaml index 980c1a1a2d..c1466bdcfc 100644 --- a/docs/subsystems/session-reference.i18n.yaml +++ b/docs/subsystems/session-reference.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-reference.md -session-reference.md: 60e29c56ea372e100009b94caedea5327308432a -session-reference.zh.md: 3ae9264abc15848646e5397dfcbeff764c038918 +session-reference.md: f539a59b8d26182aff9746b6d6a39a86ba15cb45 +session-reference.zh.md: 82b6a6c5ec5c91372fc6a43b73ad1572fccf5b3a diff --git a/docs/subsystems/session-reference.md b/docs/subsystems/session-reference.md index 60e29c56ea..f539a59b8d 100644 --- a/docs/subsystems/session-reference.md +++ b/docs/subsystems/session-reference.md @@ -102,7 +102,7 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) diff --git a/docs/subsystems/session-reference.zh.md b/docs/subsystems/session-reference.zh.md index 3ae9264abc..82b6a6c5ec 100644 --- a/docs/subsystems/session-reference.zh.md +++ b/docs/subsystems/session-reference.zh.md @@ -102,7 +102,7 @@ async listCandidates( agent: Agent, query: string = '', limit: number = this.con async prepare( agent: Agent, content: ContentBlock[], references: SessionReferenceInput[], signal?: AbortSignal, ): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) Source: [`packages/context/session-reference/src/index.ts:70`](../../packages/context/session-reference/src/index.ts) diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index e6ead80f87..a58335b9f1 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/subagent.md -subagent.md: cbf9043b36a5cb2ab37bb99c6dbbf5a18d1e0288 -subagent.zh.md: ffd8fefd8b9d128c21e1b91ee0ba1fb706638fa9 +subagent.md: c132c073459642b133e0d7e662ee98a8c9dfb694 +subagent.zh.md: e46f9aa98389842918ae37dda1477ef1b565e774 diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index cbf9043b36..c132c07345 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -611,7 +611,7 @@ list(): string[] async start(name: string, request: SubagentStartRequest): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md) Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index ffd8fefd8b..e46f9aa983 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -613,7 +613,7 @@ list(): string[] async start(name: string, request: SubagentStartRequest): Promise ``` -Types: [Agent](core.md) · [ContentBlock](core.md) · [MessageId](core.md) · [SessionId](core.md) +Types: [Agent](core.md) · [ContentBlock](llm-streaming.md) · [MessageId](llm-streaming.md) · [SessionId](core.md) Source: [`packages/subagent/subagent/src/index.ts:167`](../../packages/subagent/subagent/src/index.ts) diff --git a/docs/subsystems/token-meter.i18n.yaml b/docs/subsystems/token-meter.i18n.yaml index e348aca7a0..17bbb937cc 100644 --- a/docs/subsystems/token-meter.i18n.yaml +++ b/docs/subsystems/token-meter.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/token-meter.md -token-meter.md: 0f53955b3e6afce84520314f4de453edaa897158 -token-meter.zh.md: 3ba22c54d599982d3f7bdbcb9160b6ffc3255e07 +token-meter.md: 8a01980191bf29b1fbc0f8b1d0b33d1fb149319c +token-meter.zh.md: 7411ce26cb75697607a706469d7923d46ff3f421 diff --git a/docs/subsystems/token-meter.md b/docs/subsystems/token-meter.md index 0f53955b3e..8a01980191 100644 --- a/docs/subsystems/token-meter.md +++ b/docs/subsystems/token-meter.md @@ -84,7 +84,7 @@ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement estimateMessage(message: Message): number ``` -Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) +Types: [EpochHeader](session.md) · [Message](llm-streaming.md) · [Session](session.md) Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) diff --git a/docs/subsystems/token-meter.zh.md b/docs/subsystems/token-meter.zh.md index 3ba22c54d5..7411ce26cb 100644 --- a/docs/subsystems/token-meter.zh.md +++ b/docs/subsystems/token-meter.zh.md @@ -84,7 +84,7 @@ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement estimateMessage(message: Message): number ``` -Types: [EpochHeader](session.md) · [Message](core.md) · [Session](session.md) +Types: [EpochHeader](session.md) · [Message](llm-streaming.md) · [Session](session.md) Source: [`packages/llm/token-meter/src/index.ts:74`](../../packages/llm/token-meter/src/index.ts) diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index 8b0aa47c9a..003e175399 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/tools.md -tools.md: 5d72bd54f0dc5995218087030f24c467c8ccbb38 -tools.zh.md: 2340d4c2d92c51aa9cc89f4694b626c76d05140b +tools.md: b63014d1c2058c8fc7e96817d4bca858a1954482 +tools.zh.md: 2dc5802efce25a732a60c660191cd584ede623bb diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index 5d72bd54f0..b63014d1c2 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -596,7 +596,7 @@ Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bri 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise ``` -Types: [ContentBlock](core.md) · [Scoped](scope.md) +Types: [ContentBlock](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index 2340d4c2d9..2dc5802efc 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -596,7 +596,7 @@ Shape the DURABLE LOG COPY of one `run_code` sub-dispatch outcome before the bri 'tools/code-dispatch-log'(this: Scoped, dispatch: CodeDispatchLog, next: () => Promise): Promise ``` -Types: [ContentBlock](core.md) · [Scoped](scope.md) +Types: [ContentBlock](llm-streaming.md) · [Scoped](scope.md) Source: [`packages/core/tools/src/index.ts:173`](../../packages/core/tools/src/index.ts) diff --git a/packages/acp/README.i18n.yaml b/packages/acp/README.i18n.yaml index ca4ec66f67..cd4555752b 100644 --- a/packages/acp/README.i18n.yaml +++ b/packages/acp/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/acp/README.md -README.md: 3ba247598f29f2244456061fe7f9a3282086148f -README.zh.md: c13bc05b12fff2ef97b4d547936aa14a6556430a +README.md: 97af6d164b265bf0e98e3c9f5a444cffad4face5 +README.zh.md: 266b108c108b9f543d93cbb727ddbc01ab4bef36 diff --git a/packages/acp/README.md b/packages/acp/README.md index 3ba247598f..97af6d164b 100644 --- a/packages/acp/README.md +++ b/packages/acp/README.md @@ -2,10 +2,10 @@ English | [中文](README.zh.md) -The ACP group exposes harness agents to programmatic clients. It is an interoperability transport, not a presentation or human-interaction layer. +The ACP group exposes harness agents to programmatic clients over the Agent Client Protocol. It is an interoperability transport, not a presentation or human-interaction layer; the matching out-of-process subagent *client* lives in [`subagent/subagent-acp`](../subagent/subagent-acp/README.md) because it implements the subagent provider interface. | Package | Role | |---|---| | [`acp/`](acp/README.md) | Automation-only ACP server. | -The matching out-of-process subagent client remains in [`subagent/subagent-acp`](../subagent/subagent-acp/README.md) because it implements the subagent provider interface; arbitrary ACP clients may drive the same server contract. +The server contract is documented in [`acp/README.md`](acp/README.md). diff --git a/packages/acp/README.zh.md b/packages/acp/README.zh.md index c13bc05b12..266b108c10 100644 --- a/packages/acp/README.zh.md +++ b/packages/acp/README.zh.md @@ -2,10 +2,10 @@ [English](README.md) | 中文 -ACP(Agent Client Protocol)组将 harness 中的 agent(智能体)公开给程序化客户端。它是互操作传输层,而非展示层或人机交互层。 +ACP 组通过 Agent Client Protocol 把 harness agent 暴露给编程客户端。它是互操作传输层,不是展示或人机交互层;配对的进程外 subagent *客户端*在 [`subagent/subagent-acp`](../subagent/subagent-acp/README.md),因为它实现的是 subagent 提供方接口。 | 包 | 职责 | |---|---| | [`acp/`](acp/README.md) | 仅面向自动化的 ACP 服务器。 | -与之匹配的进程外 subagent 客户端仍位于 [`subagent/subagent-acp`](../subagent/subagent-acp/README.md),因为它实现 subagent 提供方接口;任意 ACP 客户端都可以按照同一服务器契约驱动该服务器。 +服务器契约见 [`acp/README.md`](acp/README.md)。 diff --git a/packages/bash/README.i18n.yaml b/packages/bash/README.i18n.yaml index 4c4a2c6180..00321c8ab0 100644 --- a/packages/bash/README.i18n.yaml +++ b/packages/bash/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/bash/README.md -README.md: 601782caad24d3555a206365a3f1954d81af1cf0 -README.zh.md: 8b96c4f80ba8776bfd8bdde178178cea946ff36c +README.md: 7395a051742af24b96842966b71297dc01853dca +README.zh.md: 1d5825e077c46284dbd4895e285cda8f39c252a3 diff --git a/packages/bash/README.md b/packages/bash/README.md index 601782caad..7395a05174 100644 --- a/packages/bash/README.md +++ b/packages/bash/README.md @@ -15,3 +15,5 @@ The capability family spans the canonical executor seam, its implementations, th | [`tool-pwsh/`](tool-pwsh/README.md) | Exposes PowerShell execution to the model. | (registers on `ctx.tools`) | A leaf `cordis.yml` selects one executor implementation and the model-facing tools it needs. A sandboxed composition also selects a `ctx.sandbox` provider; the [ACP example](../../examples/acp-agent/) shows one complete wiring. + +The subsystem reference — request/spec vocabulary, results, background processes, the service, and events — is [docs/subsystems/bash.md](../../docs/subsystems/bash.md). diff --git a/packages/bash/README.zh.md b/packages/bash/README.zh.md index 8b96c4f80b..1d5825e077 100644 --- a/packages/bash/README.zh.md +++ b/packages/bash/README.zh.md @@ -15,3 +15,5 @@ | [`tool-pwsh/`](tool-pwsh/README.md) | 向模型公开 PowerShell 执行。 | (注册到 `ctx.tools`) | 叶节点 `cordis.yml` 选择一个执行器实现和所需的面向模型工具。沙箱化组合还会选择一个 `ctx.sandbox` 提供方;[ACP(Agent Client Protocol)示例](../../examples/acp-agent/)展示一套完整接线。 + +子系统参考——请求/spec 词汇、结果、后台进程、服务与事件——见 [docs/subsystems/bash.md](../../docs/subsystems/bash.md)。 diff --git a/packages/boot/README.i18n.yaml b/packages/boot/README.i18n.yaml index 151e5e5c08..8f691dd089 100644 --- a/packages/boot/README.i18n.yaml +++ b/packages/boot/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/boot/README.md -README.md: 7727f4fbab62a930051fd3b92d04513ac9f5d218 -README.zh.md: aa9a56619ee55c3be8feb58881f77c460861b1ac +README.md: 5e4e483b60adab0b22ddb5279f4cd8fb699b9c35 +README.zh.md: d49afa45b9fb93f6fa83944167057f0c933cd05d diff --git a/packages/boot/README.md b/packages/boot/README.md index 7727f4fbab..5e4e483b60 100644 --- a/packages/boot/README.md +++ b/packages/boot/README.md @@ -2,8 +2,10 @@ English | [中文](README.zh.md) -The channel-neutral boot library the app bins share. A role-complete single-package group: it belongs to no channel and no assembly — `apps/cli`, the [`scaffold/`](../scaffold/README.md) launcher, and the [`examples/`](../examples/README.md) demo bins all consume it. +The channel-neutral boot library the app bins share: `apps/cli`, the [`scaffold/`](../scaffold/README.md) launcher, and the [`examples/`](../examples/README.md) demo bins all consume it. | Package | Role | ctx key | |---|---|---| | `app-boot/` | Shared boot glue for the app bins: `.env` loading, fail-loud Loader guards, snapshot-aware config resolution, the settle-the-tree boot sequence | (library for the bins) | + +The boot sequence and personal-config contract are documented in [`app-boot/README.md`](app-boot/README.md). diff --git a/packages/boot/README.zh.md b/packages/boot/README.zh.md index aa9a56619e..d49afa45b9 100644 --- a/packages/boot/README.zh.md +++ b/packages/boot/README.zh.md @@ -2,8 +2,10 @@ [English](README.md) | 中文 -各 app bin 共享、与通道无关的启动库。一个角色完备的单包组:它不归属任何通道也不归属任何组装——`apps/cli`、[`scaffold/`](../scaffold/README.md) 启动器和 [`examples/`](../examples/README.md) 各演示 bin 都消费它。 +各 app bin 共享的、与渠道无关的启动库:`apps/cli`、[`scaffold/`](../scaffold/README.md) 启动器与 [`examples/`](../examples/README.md) demo bin 都消费它。 | 包 | 职责 | ctx 键 | |---|---|---| | `app-boot/` | app bin 的共享启动粘合层:加载 `.env`、会明确报错的 Loader 保护机制、感知快照的配置解析,以及等待整棵树停稳的启动序列 | (供各 bin 使用的库) | + +启动序列与个人配置契约见 [`app-boot/README.md`](app-boot/README.md)。 diff --git a/packages/client/README.i18n.yaml b/packages/client/README.i18n.yaml index e65d5246a4..2ce3e44fa7 100644 --- a/packages/client/README.i18n.yaml +++ b/packages/client/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/client/README.md -README.md: b6fa426fbe541e2b22d2bf5f19d4397361cf0899 -README.zh.md: 5a55bb8c2c31b5215fc73e75e1c4f3aca79add64 +README.md: 56b9363cc724515ecbd11127ea4c13aba84283df +README.zh.md: edd378bf80b63e9ab2481ab555320ed18de85139 diff --git a/packages/client/README.md b/packages/client/README.md index b6fa426fbe..56b9363cc7 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -38,3 +38,5 @@ The browser side of the dsh web GUI: shell boot, browser-host communication, sha | [`ui-models/`](ui-models/README.md) | Provides model-provider configuration and DeepSeek onboarding. | Each child reference owns its contract and detailed behavior. The [slot system standard](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) and [web client architecture note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) own the cross-package composition and loading decisions. + +The subsystem reference is [client-modules.md](../../docs/subsystems/client-modules.md); the [slot system standard](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md) is the definitive slot model, and the [web client architecture note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md) owns the loading chain and object layer. diff --git a/packages/client/README.zh.md b/packages/client/README.zh.md index 5a55bb8c2c..edd378bf80 100644 --- a/packages/client/README.zh.md +++ b/packages/client/README.zh.md @@ -38,3 +38,5 @@ dsh web GUI 的浏览器侧:shell 启动、浏览器与宿主通信、共享 U | [`ui-models/`](ui-models/README.md) | 提供模型提供方配置与 DeepSeek 配置引导。 | 每个子文档负责自身的契约和详细行为。[slot 系统标准](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md)与 [Web 客户端架构 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md)负责跨包组合与加载决策。 + +子系统参考是 [client-modules.md](../../docs/subsystems/client-modules.md);[slot 系统标准](../../.agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md)是权威 slot 模型,[web 客户端架构说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md)拥有加载链与对象层。 diff --git a/packages/code-runtime/README.i18n.yaml b/packages/code-runtime/README.i18n.yaml index 3eb462d513..1f9b94c59b 100644 --- a/packages/code-runtime/README.i18n.yaml +++ b/packages/code-runtime/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/code-runtime/README.md -README.md: f20a287419b94b1a9dc1d8da7303fc4d3032cfd3 -README.zh.md: f5cd4c9949f2bd7a7d6d7cd078144910712a3819 +README.md: 45dd55b8609fd33b7116f6b2ea36df3b3332e419 +README.zh.md: ebf288a49f87ad01b1ec80c3843c87c91206af26 diff --git a/packages/code-runtime/README.md b/packages/code-runtime/README.md index f20a287419..45dd55b860 100644 --- a/packages/code-runtime/README.md +++ b/packages/code-runtime/README.md @@ -10,3 +10,5 @@ The code-execution capability seam (see [capability seams](../../.agents/notes/i | [`code-runtime-worker/`](code-runtime-worker/README.md) | Worker-thread backend | registers `ctx.codeRuntime` | Backends register the seam without changing its consumer. The child READMEs own language, isolation, and execution-budget details. + +The subsystem reference — run requests/results, binding namespaces, the failure taxonomy — is [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.md). diff --git a/packages/code-runtime/README.zh.md b/packages/code-runtime/README.zh.md index f5cd4c9949..ebf288a49f 100644 --- a/packages/code-runtime/README.zh.md +++ b/packages/code-runtime/README.zh.md @@ -10,3 +10,5 @@ | [`code-runtime-worker/`](code-runtime-worker/README.md) | Worker 线程后端 | 注册 `ctx.codeRuntime` | 后端在不改变消费方的情况下注册该 seam。子 README 负责语言、隔离和执行预算细节。 + +子系统参考——运行请求/结果、绑定命名空间、失败分类体系——见 [docs/subsystems/code-runtime.md](../../docs/subsystems/code-runtime.md)。 diff --git a/packages/compact/README.i18n.yaml b/packages/compact/README.i18n.yaml index 17357bea7b..bb5922ee75 100644 --- a/packages/compact/README.i18n.yaml +++ b/packages/compact/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/compact/README.md -README.md: 509ea2764250f42e492f787deba959a8dd9967b7 -README.zh.md: adc52690dbe9e904d8f24199cb78493dab3333a4 +README.md: 0a83b87f57e4c948ee6df9e622f17964aff00f0f +README.zh.md: cba53ee7bb917a981a0fd4b77d833b8fb7dd2c3f diff --git a/packages/compact/README.md b/packages/compact/README.md index 509ea27642..0a83b87f57 100644 --- a/packages/compact/README.md +++ b/packages/compact/README.md @@ -12,3 +12,5 @@ A compaction capability family (see [capability seams](../../.agents/notes/imple | [`command-compact/`](command-compact/README.md) | Human compaction command | registers on `ctx.commands` | The backend, optional pruner, and human command compose through the seam; token measurement remains a separate LLM-family service. The [compaction capability-seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) owns the dependency rationale. + +The subsystem reference — the `compact/*` events, `CompactionResult`, the service, pruning outcomes — is [docs/subsystems/compaction.md](../../docs/subsystems/compaction.md); the seam's deliberate `dsh-session`/`dsh-llm` dependency is recorded in the [compaction capability-seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md). diff --git a/packages/compact/README.zh.md b/packages/compact/README.zh.md index adc52690db..cba53ee7bb 100644 --- a/packages/compact/README.zh.md +++ b/packages/compact/README.zh.md @@ -12,3 +12,5 @@ | [`command-compact/`](command-compact/README.md) | 用户压缩命令 | 注册到 `ctx.commands` | 后端、可选修剪器和用户命令通过该 seam 组合;token 测量仍是独立的 LLM(大语言模型)家族服务。[压缩能力 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)负责依赖关系的设计原理。 + +子系统参考——`compact/*` 事件、`CompactionResult`、服务、剪除结果——见 [docs/subsystems/compaction.md](../../docs/subsystems/compaction.md);seam 有意依赖 `dsh-session`/`dsh-llm` 的决定记录在[压缩能力 seam Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)。 diff --git a/packages/context/README.i18n.yaml b/packages/context/README.i18n.yaml index f3acd7f2bf..efb18426a4 100644 --- a/packages/context/README.i18n.yaml +++ b/packages/context/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/context/README.md -README.md: a0b5eef51740b3aa67b9e158a00357f849b8bf57 -README.zh.md: d6ed8050f3a64f740a951883f0dc321ac08e978a +README.md: 237f6191329f89834ec9bd9f72c0a1fd1d94651e +README.zh.md: 7c55dd91dbf7e3fca222e4b6f6bed2769f5c253c diff --git a/packages/context/README.md b/packages/context/README.md index a0b5eef517..237f619132 100644 --- a/packages/context/README.md +++ b/packages/context/README.md @@ -12,3 +12,5 @@ Product plugins that add model-visible request context without defining a tool. | [`workspace-context/`](workspace-context/README.md) | Workspace-instruction context | — | The [`workspace-context` decision record](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) explains its per-agent/session isolation and lifecycle split. + +Session references are documented in [docs/subsystems/session-reference.md](../../docs/subsystems/session-reference.md); the [`workspace-context` decision record](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) owns its per-agent/session isolation and lifecycle split. diff --git a/packages/context/README.zh.md b/packages/context/README.zh.md index d6ed8050f3..7c55dd91db 100644 --- a/packages/context/README.zh.md +++ b/packages/context/README.zh.md @@ -12,3 +12,5 @@ | [`workspace-context/`](workspace-context/README.md) | workspace 指令上下文 | — | [`workspace-context` 决策记录](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)解释其逐 agent(智能体)/会话隔离和生命周期拆分。 + +会话引用见 [docs/subsystems/session-reference.md](../../docs/subsystems/session-reference.md);[`workspace-context` 决策记录](../../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)拥有其按 agent/会话隔离与生命周期拆分。 diff --git a/packages/core/README.i18n.yaml b/packages/core/README.i18n.yaml index d43caa011e..aa542fe885 100644 --- a/packages/core/README.i18n.yaml +++ b/packages/core/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/core/README.md -README.md: d51ef73f7f545920f8cd527b2b0d597305ae86d0 -README.zh.md: 729f41fafb6b9585a66e58fe8d6ac63c1f235acc +README.md: 40a888183a9338805d83c86bab9ae16f1c7bc04f +README.zh.md: 496d547a173a63ac9d8d2ce0f005877dab92d358 diff --git a/packages/core/README.md b/packages/core/README.md index d51ef73f7f..40a888183a 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -16,3 +16,5 @@ The session log, system-prompt assembly, tool registry, agent vocabulary, and co `scope` supplies the shared scoping primitive. `agent` owns the public seam, while `agent-loop` is its default implementation; extension plugins depend on the seam so the driver remains swappable. Runnable compositions belong to [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md); this group owns only the swappable spine pieces. + +The subsystem reference — the package-by-package loop map, the `Agent` handle and its delivery/interception contracts — is [docs/subsystems/core.md](../../docs/subsystems/core.md); the default runnable composition is [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md). diff --git a/packages/core/README.zh.md b/packages/core/README.zh.md index 729f41fafb..496d547a17 100644 --- a/packages/core/README.zh.md +++ b/packages/core/README.zh.md @@ -16,3 +16,5 @@ `scope` 提供共享作用域原语。`agent` 负责公开 seam,`agent-loop` 是其默认实现;扩展插件依赖该 seam,从而保持驱动器可替换。 可运行组合属于 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md);该分组只负责可替换的主干组件。 + +子系统参考——逐包循环地图、`Agent` 句柄及其投递/拦截契约——见 [docs/subsystems/core.md](../../docs/subsystems/core.md);默认可运行组合是 [`examples/agent-spine-demo`](../examples/agent-spine-demo/README.md)。 diff --git a/packages/credentials/README.i18n.yaml b/packages/credentials/README.i18n.yaml index 04f14e8e75..38cbbb2968 100644 --- a/packages/credentials/README.i18n.yaml +++ b/packages/credentials/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/credentials/README.md -README.md: c08831c90333f8515bf86a5af717c38c50b50817 -README.zh.md: c9756c010af5d2db0fc41f3108eabdc7f1161293 +README.md: 75e3941ff6421ca7e41a71ab2bfe8b30209ce598 +README.zh.md: d7c958689fdffc82830a82b04e2a06802bc81634 diff --git a/packages/credentials/README.md b/packages/credentials/README.md index c08831c903..75e3941ff6 100644 --- a/packages/credentials/README.md +++ b/packages/credentials/README.md @@ -10,3 +10,5 @@ The credential capability family separates reference resolution from its provide | [`credentials-local/`](credentials-local/README.md) | Environment and local-file provider | registers `ctx.credentials` | Configuration carries references, not secret values. Consumers resolve those references at their operation boundary; the child READMEs own mutation, precedence, and storage semantics. + +The subsystem reference — `CredentialRef`, per-operation resolution, UI-safe `CredentialInfo`, provider layers — is [docs/subsystems/credentials.md](../../docs/subsystems/credentials.md). diff --git a/packages/credentials/README.zh.md b/packages/credentials/README.zh.md index c9756c010a..d7c958689f 100644 --- a/packages/credentials/README.zh.md +++ b/packages/credentials/README.zh.md @@ -10,3 +10,5 @@ | [`credentials-local/`](credentials-local/README.md) | 环境与本地文件提供方 | 注册 `ctx.credentials` | 配置携带引用而非机密值。消费方在其操作边界解析这些引用;变更、优先级与存储语义由子级 README 负责。 + +子系统参考——`CredentialRef`、按操作解析、对 UI 安全的 `CredentialInfo`、提供方层——见 [docs/subsystems/credentials.md](../../docs/subsystems/credentials.md)。 diff --git a/packages/fs/README.i18n.yaml b/packages/fs/README.i18n.yaml index f4719447aa..b1e493f3c8 100644 --- a/packages/fs/README.i18n.yaml +++ b/packages/fs/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/fs/README.md -README.md: b15012e882b60847e1ad22edf08d1202ba64fe5b -README.zh.md: 628f6c74894bc67559d49f7cf5d1378d0ece2382 +README.md: 162e2c75e92bc4ea6aba377c14db62e8ce83fbb9 +README.zh.md: b2929b17be6ce90051f0808c2cdf4bbf643f8f13 diff --git a/packages/fs/README.md b/packages/fs/README.md index b15012e882..162e2c75e9 100644 --- a/packages/fs/README.md +++ b/packages/fs/README.md @@ -19,3 +19,5 @@ The interface lives at `fs/fs/`. A sandboxed, remote, or project-scoped filesyst ## No timeouts on file IO `read`/`write`/`edit` take **no** `timeoutMs`, and the provider seam arms no deadline — unlike bash and web (which consume [`@deepseek-ai/dsh-timeout`](../util/timeout/README.md)) and the bash-backed `glob`/`grep` (whose declared `timeoutMs` is enforced by `@deepseek-ai/dsh-timeout-policy`): those are process-backed, where a deadline can really kill the work. A local syscall is best-effort-abortable at most: a timeout could not force an in-progress `fsync`/`rename` to stop, so a deadline here would be a knob that cannot deliver on its promise. Adding one would also be an implicit default in the exact place explicit-over-implicit forbids. Both reference agents (Claude Code, Codex) leave file IO untimed for the same reason; cancellation still propagates through the tool-execution signal for best-effort abort at syscall boundaries. + +The subsystem reference — targets, outcomes, guards, policy events, the error taxonomy, and why file IO takes no timeout — is [docs/subsystems/filesystem.md](../../docs/subsystems/filesystem.md); the sandbox fence in the [cross-family fs sandbox Agent Note](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md). diff --git a/packages/fs/README.zh.md b/packages/fs/README.zh.md index 628f6c7489..b2929b17be 100644 --- a/packages/fs/README.zh.md +++ b/packages/fs/README.zh.md @@ -19,3 +19,5 @@ ## 文件 I/O 不设超时 `read`/`write`/`edit` **不** 接受 `timeoutMs`,提供方 seam 也不启动 deadline。这与 bash 和 web(两者使用 [`@deepseek-ai/dsh-timeout`](../util/timeout/README.md))及基于 bash 的 `glob`/`grep` 不同(它们声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):这些工作由进程支持,deadline 可以实际终止工作。本地系统调用至多只能尽力中止:超时无法强制正在进行的 `fsync`/`rename` 停止,因此这里的 deadline 会成为无法兑现承诺的配置项。在此添加 deadline 还会在「显式优于隐式」明确禁止的地方引入隐式默认值。两个参考 agent(Claude Code、Codex)出于同一原因都不为文件 I/O 计时;取消仍通过工具执行信号传播,在系统调用边界尽力中止。 + +子系统参考——目标、结果、防护、策略事件、错误分类体系,以及文件 IO 为何不设超时——见 [docs/subsystems/filesystem.md](../../docs/subsystems/filesystem.md);沙箱围栏见[跨家族 fs 沙箱 Agent Note](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md)。 diff --git a/packages/goal/README.i18n.yaml b/packages/goal/README.i18n.yaml index 4238bac3c4..d2a6bf0248 100644 --- a/packages/goal/README.i18n.yaml +++ b/packages/goal/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/goal/README.md -README.md: 9fc6b0c18b1862a8be08275785ea3b6185e9bdbc -README.zh.md: 08f15bcc4e405e25d4dfd5981bbc99408833403c +README.md: 9d307278b39ecee61df9feace9fec4235b45d794 +README.zh.md: 7217c174ba5382c53204ae3678be42375fc63db5 diff --git a/packages/goal/README.md b/packages/goal/README.md index 9fc6b0c18b..9d307278b3 100644 --- a/packages/goal/README.md +++ b/packages/goal/README.md @@ -2,7 +2,7 @@ English | [中文](README.zh.md) -The goal family owns durable objective state independently of the model-facing tools and continuation policy that consume it. +Durable objective state for an agent session, owned independently of the model-facing tools and continuation policy that consume it. Goal state is part of the owning session log; consumers depend on `dsh-goal`, never on the concrete agent loop. | Package | Role | ctx key | |---|---|---| @@ -11,4 +11,4 @@ The goal family owns durable objective state independently of the model-facing t | [`tool-goal/`](tool-goal/README.md) | Model-facing goal tools | — | | [`command-goal/`](command-goal/README.md) | Human-facing goal command | — | -Goal state is part of the owning session log. Consumers depend on `dsh-goal`, not on the concrete agent loop; continuation behavior belongs in a separate plugin on the public agent seams. +The subsystem reference — goal identity, lifecycle snapshots, activation, change records — is [docs/subsystems/goal.md](../../docs/subsystems/goal.md). diff --git a/packages/goal/README.zh.md b/packages/goal/README.zh.md index 08f15bcc4e..7217c174ba 100644 --- a/packages/goal/README.zh.md +++ b/packages/goal/README.zh.md @@ -2,7 +2,7 @@ [English](README.md) | 中文 -goal 家族负责持久目标状态,与消费该状态的面向模型工具和续行策略相互独立。 +agent 会话的持久目标状态,独立于消费它的模型工具与续跑策略。goal 状态是所属会话日志的一部分;消费方依赖 `dsh-goal`,绝不依赖具体 agent loop。 | 包 | 职责 | ctx 键 | |---|---|---| @@ -11,4 +11,4 @@ goal 家族负责持久目标状态,与消费该状态的面向模型工具和 | [`tool-goal/`](tool-goal/README.md) | 面向模型的目标工具 | 无 | | [`command-goal/`](command-goal/README.md) | 面向用户的目标命令 | 无 | -目标状态是其所属会话日志的一部分。消费方依赖 `dsh-goal`,而不是具体的 agent loop(智能体循环);续行行为由基于公开 agent seam 的独立插件负责。 +子系统参考——goal 标识、生命周期快照、激活、变更记录——见 [docs/subsystems/goal.md](../../docs/subsystems/goal.md)。 diff --git a/packages/guard/README.i18n.yaml b/packages/guard/README.i18n.yaml index 8251a76ed5..b99cec734b 100644 --- a/packages/guard/README.i18n.yaml +++ b/packages/guard/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/guard/README.md -README.md: 5791474a2fd6aeda728d4c335c930069ade6e023 -README.zh.md: 82d779d2f1834317672b8fb03f2a4a875bb566fc +README.md: f0a80b1f497ee5b2459bebb55f886b42d74e4323 +README.zh.md: c7747e2febcb1656925fd556743eb2dd28090e37 diff --git a/packages/guard/README.md b/packages/guard/README.md index 5791474a2f..f0a80b1f49 100644 --- a/packages/guard/README.md +++ b/packages/guard/README.md @@ -8,3 +8,5 @@ Behavioral guard plugins watch the agent loop for unproductive patterns and enfo |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | Advisory reminders for repeated tool calls | listens on tool and agent events | | [`timeout-policy/`](timeout-policy/README.md) | Arms per-call tool deadlines as deployment policy | registers a `tools/execute` listener | + +Reminders travel as `additionalContexts` on the `tools/post-execute` decision and are appended as logged plugin-sourced `user/message` events ([tools](../../docs/subsystems/tools.md)); the timeout split across `dsh-timeout`, capability termination, and this policy layer is recorded in the [timeout-library Agent Note](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md). diff --git a/packages/guard/README.zh.md b/packages/guard/README.zh.md index 82d779d2f1..c7747e2feb 100644 --- a/packages/guard/README.zh.md +++ b/packages/guard/README.zh.md @@ -8,3 +8,5 @@ |---|---|---| | [`repeat-tool-guard/`](repeat-tool-guard/README.md) | 针对重复工具调用的建议性提醒 | 监听工具和 agent 事件 | | [`timeout-policy/`](timeout-policy/README.md) | 以部署策略形式设置单次工具调用截止时间 | 注册 `tools/execute` 监听器 | + +提醒作为 `additionalContexts` 随 `tools/post-execute` 决策传递,并以插件来源的 `user/message` 事件记录([工具](../../docs/subsystems/tools.md));跨 `dsh-timeout`、能力方终止与本策略层的超时拆分记录在[超时库 Agent Note](../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)。 diff --git a/packages/host/README.i18n.yaml b/packages/host/README.i18n.yaml index 1aaacd7ecb..0859a2571d 100644 --- a/packages/host/README.i18n.yaml +++ b/packages/host/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/host/README.md -README.md: 269a27f51c842f13bc11c175916b7be22db72bd2 -README.zh.md: 559bf785eb45d59a30f676b98c14143c69d57edd +README.md: 926cb0b6b87a8ee76cb2dab745a31f620f4e7f5c +README.zh.md: dc617084c756b80716f9a02bd2d22ba6954030f6 diff --git a/packages/host/README.md b/packages/host/README.md index 269a27f51c..926cb0b6b8 100644 --- a/packages/host/README.md +++ b/packages/host/README.md @@ -15,3 +15,5 @@ The host side of the dsh web GUI: the API gateway every client shape shares, and | [`directory-picker-auto/`](directory-picker-auto/README.md) | Host-adaptive picker composition | mounts a backend | `apiproxy` remains transport-independent; [`client/connection`](../client/connection/README.md) supplies the browser/HTTP carrier. Picker implementations replace one another behind the shared seam. + +The subsystem references: [http-server.md](../../docs/subsystems/http-server.md) and [workspace.md](../../docs/subsystems/workspace.md) (the picker seam). diff --git a/packages/host/README.zh.md b/packages/host/README.zh.md index 559bf785eb..dc617084c7 100644 --- a/packages/host/README.zh.md +++ b/packages/host/README.zh.md @@ -15,3 +15,5 @@ dsh Web GUI 的宿主侧:所有客户端形态共享的 API 网关,以及承 | [`directory-picker-auto/`](directory-picker-auto/README.md) | 宿主自适应选择器组合 | 挂载一个后端 | `apiproxy` 保持传输无关;[`client/connection`](../client/connection/README.md) 提供浏览器/HTTP 载体。选择器实现可在共享 seam 后互相替换。 + +子系统参考:[http-server.md](../../docs/subsystems/http-server.md) 与 [workspace.md](../../docs/subsystems/workspace.md)(挑选 seam)。 diff --git a/packages/interaction/README.i18n.yaml b/packages/interaction/README.i18n.yaml index 2555e896a1..77539b60fb 100644 --- a/packages/interaction/README.i18n.yaml +++ b/packages/interaction/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/README.md -README.md: 506b47753fc9250f9f7bd971265421ad46a106b2 -README.zh.md: 34d336f3d3c10deab166d5c13bb86c13ee9bced0 +README.md: 31a306cb08fe69297b0d0843b57b5e491c0bbe44 +README.zh.md: 6eafe8726ba59edd3a6bf739deacc037a9b56b76 diff --git a/packages/interaction/README.md b/packages/interaction/README.md index 506b47753f..31a306cb08 100644 --- a/packages/interaction/README.md +++ b/packages/interaction/README.md @@ -13,3 +13,5 @@ The seams through which a human collaborates with a running agent — questions, | [`tool-ask-user/`](tool-ask-user/README.md) | Exposes human questions to the model. | (registers on `ctx.tools`) | These packages integrate through existing agent and session contracts rather than changing the loop. Interactive applications provide the concrete command, approval, and question adapters; automation uses [`acp/`](../acp/README.md), and runnable demo bundles live under [`examples/`](../examples/README.md). The product [`dsh`](../../apps/cli/README.md) CLI composes these packages directly. + +The subsystem references: [approval.md](../../docs/subsystems/approval.md), [permission.md](../../docs/subsystems/permission.md), [user-interaction.md](../../docs/subsystems/user-interaction.md), [commands.md](../../docs/subsystems/commands.md), and [tui.md](../../docs/subsystems/tui.md). The automation-only ACP transport is [`acp/`](../acp/README.md), the SDK's JSON-RPC server half [`scaffold/server`](../scaffold/README.md), and the shared bin boot glue [`boot/`](../boot/README.md). diff --git a/packages/interaction/README.zh.md b/packages/interaction/README.zh.md index 34d336f3d3..6eafe8726b 100644 --- a/packages/interaction/README.zh.md +++ b/packages/interaction/README.zh.md @@ -13,3 +13,5 @@ | [`tool-ask-user/`](tool-ask-user/README.md) | 向模型公开用户问题。 | (注册到 `ctx.tools`) | 这些包通过现有的 agent(智能体)和会话契约集成,而不改变循环。交互式应用提供具体的命令、审批和提问适配器;自动化使用 [`acp/`](../acp/README.md),可运行的演示组合包位于 [`examples/`](../examples/README.md)。产品 [`dsh`](../../apps/cli/README.md) CLI(命令行界面)直接组合这些包。 + +子系统参考:[approval.md](../../docs/subsystems/approval.md)、[permission.md](../../docs/subsystems/permission.md)、[user-interaction.md](../../docs/subsystems/user-interaction.md)、[commands.md](../../docs/subsystems/commands.md) 与 [tui.md](../../docs/subsystems/tui.md)。仅自动化的 ACP 传输在 [`acp/`](../acp/README.md),SDK 的 JSON-RPC 服务器一半在 [`scaffold/server`](../scaffold/README.md),共享 bin 启动胶水在 [`boot/`](../boot/README.md)。 diff --git a/packages/llm/README.i18n.yaml b/packages/llm/README.i18n.yaml index 3c4ae19900..a35bf870d4 100644 --- a/packages/llm/README.i18n.yaml +++ b/packages/llm/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/llm/README.md -README.md: 92d9fbfa2b8c8db4700562009db49229b2189ab3 -README.zh.md: 5c6e7aad1db6511bdb660b86e257652128db131f +README.md: 077ae600c89cff80cd71830e37bda00707c1d837 +README.zh.md: e2bdad7fd0b33116498b50bfa6527974fa140fc9 diff --git a/packages/llm/README.md b/packages/llm/README.md index 92d9fbfa2b..077ae600c8 100644 --- a/packages/llm/README.md +++ b/packages/llm/README.md @@ -13,3 +13,5 @@ The LLM seam and its provider adapters. The interface package (`llm`) owns the a | [`llm-pi-ai/`](llm-pi-ai/README.md) | Multi-provider pi-ai adapter | registers on `ctx.llm` | Adapters register provider routes on the seam; retry and token measurement remain separate consumers. The child READMEs own routing, metadata, replay, and provider-wire details; the [LLM architecture decisions](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md) own the rationale. + +The subsystem reference — messages and blocks, the model request, the `StreamChunk` protocol, the adapter contract — is [docs/subsystems/llm-streaming.md](../../docs/subsystems/llm-streaming.md) (token measurement: [token-meter.md](../../docs/subsystems/token-meter.md)); see the [twin adapters](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md), [replay token meter](../../.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.md), and [routed model context](../../.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md) Agent Notes. diff --git a/packages/llm/README.zh.md b/packages/llm/README.zh.md index 5c6e7aad1d..e2bdad7fd0 100644 --- a/packages/llm/README.zh.md +++ b/packages/llm/README.zh.md @@ -13,3 +13,5 @@ LLM(大语言模型)seam 及其提供方适配器。接口包(`llm`)负 | [`llm-pi-ai/`](llm-pi-ai/README.md) | 多提供方 pi-ai 适配器 | 注册到 `ctx.llm` | 适配器在 seam 上注册提供方路由;重试与 token 测量仍是独立消费方。子 README 负责路由、元数据、回放和提供方协议细节;[LLM 架构决策](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md)负责设计原理。 + +子系统参考——消息与内容块、模型请求、`StreamChunk` 协议、适配器契约——见 [docs/subsystems/llm-streaming.md](../../docs/subsystems/llm-streaming.md)(token 计量:[token-meter.md](../../docs/subsystems/token-meter.md));另见[孪生适配器](../../.agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md)、[回放 token 计量](../../.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.md)与[按路由模型上下文](../../.agents/notes/implemented/architecture/2026-07-20-routed-model-context-and-compaction-policy.md) Agent Note。 diff --git a/packages/lsp/README.i18n.yaml b/packages/lsp/README.i18n.yaml index 9919cdaba5..be1dc838ff 100644 --- a/packages/lsp/README.i18n.yaml +++ b/packages/lsp/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/lsp/README.md -README.md: 7fbdf071735673fb0158f6fa66148be1c644a433 -README.zh.md: e059dbd80b7e38c0e447e54178162316dfd127c7 +README.md: 01c464c9993571990e0542df9fadb2848a6c9a79 +README.zh.md: fa7dddeb011bd34c1f9cbe3a9dbf746d7f163897 diff --git a/packages/lsp/README.md b/packages/lsp/README.md index 7fbdf07173..01c464c999 100644 --- a/packages/lsp/README.md +++ b/packages/lsp/README.md @@ -13,3 +13,5 @@ The language-server capability seam: an abstract LSP interface, a generic stdio The interface lives at `lsp/lsp/`. The seam exposes exactly four semantic operations — `goToDefinition`, `findReferences`, `goToImplementation`, `hover` — and no generic JSON-RPC escape hatch, so a provider swap does not change how the model asks for navigation and no protocol payload or unreviewed mutation reaches the model contract. Providers register **capabilities**, not tools; `tool-lsp` is the only owner of the model-facing name, schema, prompt guidance, and presentation. See the [LSP capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md) for the design rationale, including why documents open transiently per query, why the stdio host consumes the shared filesystem/subprocess execution world, and why extension ownership is exclusive within one runtime. + +The subsystem reference — operations, coordinates, requests/results, `LspError` — is [docs/subsystems/lsp.md](../../docs/subsystems/lsp.md); design rationale in the [LSP capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md). diff --git a/packages/lsp/README.zh.md b/packages/lsp/README.zh.md index e059dbd80b..fa7dddeb01 100644 --- a/packages/lsp/README.zh.md +++ b/packages/lsp/README.zh.md @@ -13,3 +13,5 @@ 接口位于 `lsp/lsp/`。该 seam 恰好公开四种语义操作:`goToDefinition`、`findReferences`、`goToImplementation`、`hover`,且不提供通用 JSON-RPC 逃生口;因此,替换提供方不会改变模型请求导航的方式,也不会让协议载荷或未经评审的修改进入模型契约。提供方注册的是**能力** 而非工具;`tool-lsp` 是面向模型名称、schema、提示词指引和呈现的唯一 owner。 设计原理见 [LSP 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md),其中也解释了文档为何在每次查询时临时打开、stdio 主机为何使用共享的文件系统/子进程执行环境,以及扩展名归属为何在同一运行时内互斥。 + +子系统参考——操作、坐标、请求/结果、`LspError`——见 [docs/subsystems/lsp.md](../../docs/subsystems/lsp.md);设计依据见 [LSP 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-15-lsp-capability-seam.md)。 diff --git a/packages/plan/README.i18n.yaml b/packages/plan/README.i18n.yaml index 3ae6755be6..4e34bac80c 100644 --- a/packages/plan/README.i18n.yaml +++ b/packages/plan/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/plan/README.md -README.md: 598974e105aaeaf3c35418aec0655de2a5ba6888 -README.zh.md: 0f54299af5d34555d86d82e83b92d185045e770f +README.md: 535a2b02e297d7c3bc68538fa1d03226089372e0 +README.zh.md: f068bf37b44daa0d724fbb85ad85a4d62883e847 diff --git a/packages/plan/README.md b/packages/plan/README.md index 598974e105..535a2b02e2 100644 --- a/packages/plan/README.md +++ b/packages/plan/README.md @@ -9,3 +9,5 @@ Plan mode is logged, per-agent collaboration state rather than a generic mode re | [`plan-mode/`](plan-mode/README.md) | Owns plan-mode state, guidance, commands, and review flow | `ctx.planMode` | The [plan-specific collaboration state](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md) decision records the family design. + +The subsystem reference — the `plan/mode` fold, the step-boundary flush, configuration, the exit tool — is [docs/subsystems/plan.md](../../docs/subsystems/plan.md); design in [plan-specific collaboration state](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md). diff --git a/packages/plan/README.zh.md b/packages/plan/README.zh.md index 0f54299af5..f068bf37b4 100644 --- a/packages/plan/README.zh.md +++ b/packages/plan/README.zh.md @@ -9,3 +9,5 @@ Plan mode 是按 agent(智能体)记录的协作状态,而不是通用模 | [`plan-mode/`](plan-mode/README.md) | 负责 plan mode 状态、指引、命令和评审流程 | `ctx.planMode` | [plan 专用协作状态](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)决策记录了该家族的设计。 + +子系统参考——`plan/mode` 折叠、步边界冲刷、配置、退出工具——见 [docs/subsystems/plan.md](../../docs/subsystems/plan.md);设计见[计划专属协作状态](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)。 diff --git a/packages/pty/README.i18n.yaml b/packages/pty/README.i18n.yaml index 65d541a7c1..7f4c7f1f21 100644 --- a/packages/pty/README.i18n.yaml +++ b/packages/pty/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/pty/README.md -README.md: a4f743056b4a524be9623b0f700f37e0534b463f -README.zh.md: c84ad3f1b59afcdbbd111f1b82c57c56aa24fdcf +README.md: 2afb3cd61ef4bfba460716549d41f799e419ff46 +README.zh.md: 57a5c93047ccb970f499f02c0168db7e9f71b622 diff --git a/packages/pty/README.md b/packages/pty/README.md index a4f743056b..2afb3cd61e 100644 --- a/packages/pty/README.md +++ b/packages/pty/README.md @@ -11,3 +11,5 @@ English | [中文](README.zh.md) | `tool-pty` (`@deepseek-ai/dsh-tool-pty`) | Six model-facing tools and generic task integration for background sends | registers on `ctx.tools` | The design and deferred boundaries live in the [persistent PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md). + +The subsystem reference — ids, backend/session contracts, send readiness, bounded reads — is [docs/subsystems/pty.md](../../docs/subsystems/pty.md); design and deferred boundaries in the [persistent PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md). diff --git a/packages/pty/README.zh.md b/packages/pty/README.zh.md index c84ad3f1b5..57a5c93047 100644 --- a/packages/pty/README.zh.md +++ b/packages/pty/README.zh.md @@ -11,3 +11,5 @@ | `tool-pty`(`@deepseek-ai/dsh-tool-pty`) | 6 个面向模型的工具,并为后台发送集成通用任务 | 注册到 `ctx.tools` | 设计与暂缓边界记录在[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md) 中。 + +子系统参考——id、后端/会话契约、发送就绪、有界读取——见 [docs/subsystems/pty.md](../../docs/subsystems/pty.md);设计与延后边界见[持久 PTY Agent Note](../../.agents/notes/implemented/feature/2026-07-16-persistent-pty-sessions.md)。 diff --git a/packages/sandbox/README.i18n.yaml b/packages/sandbox/README.i18n.yaml index c851d420ae..ab79d37170 100644 --- a/packages/sandbox/README.i18n.yaml +++ b/packages/sandbox/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/sandbox/README.md -README.md: 1ba56d22330c26302c9283b2319d983ebf902219 -README.zh.md: 5daf830854297fe14c6560dbcde8a380ad079a0e +README.md: 61127118c895cce728ab0b4eed387f51054a83f1 +README.zh.md: 984a677aad6ab97579abf861ea8b99f149c07d08 diff --git a/packages/sandbox/README.md b/packages/sandbox/README.md index 1ba56d2233..61127118c8 100644 --- a/packages/sandbox/README.md +++ b/packages/sandbox/README.md @@ -11,3 +11,5 @@ This family applies per-session confinement policy to process execution. It cove | [`sandbox-policy/`](sandbox-policy/README.md) | Resolves durable per-session sandbox policy | `ctx.sandboxPolicy` | See the [sandbox decision](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) for the capability boundary and the [filesystem integration decision](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) for cross-family policy use. + +The subsystem reference — modes and enforcement, per-call policy, wrapped-argv dialects, fail-closed errors — is [docs/subsystems/sandbox.md](../../docs/subsystems/sandbox.md); the boundary and the cross-family phase live in the [sandbox](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md) and [cross-family fs sandbox](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) Agent Notes. diff --git a/packages/sandbox/README.zh.md b/packages/sandbox/README.zh.md index 5daf830854..984a677aad 100644 --- a/packages/sandbox/README.zh.md +++ b/packages/sandbox/README.zh.md @@ -11,3 +11,5 @@ | [`sandbox-policy/`](sandbox-policy/README.md) | 解析持久的逐会话沙箱策略 | `ctx.sandboxPolicy` | [沙箱决策](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)记录了能力边界,[文件系统集成决策](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md)记录了跨家族策略的使用方式。 + +子系统参考——模式与强制执行、按调用策略、包装 argv 方言、故障关闭错误——见 [docs/subsystems/sandbox.md](../../docs/subsystems/sandbox.md);边界与跨家族阶段见[沙箱](../../.agents/notes/implemented/feature/2026-07-06-sandbox.md)与[跨家族 fs 沙箱](../../.agents/notes/implemented/feature/2026-07-14-cross-family-fs-sandbox.md) Agent Note。 diff --git a/packages/self-modification/README.i18n.yaml b/packages/self-modification/README.i18n.yaml index 9a6a070b95..53bbf9cdbb 100644 --- a/packages/self-modification/README.i18n.yaml +++ b/packages/self-modification/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/self-modification/README.md README.md: 53f700993cce8df8729fba5a92a9498fff2412cd -README.zh.md: 9874ead263e9860b94992cb176be3c6f8c454ece +README.zh.md: 90030cda33f47d128041b7619546ac1309b8465a diff --git a/packages/self-modification/README.zh.md b/packages/self-modification/README.zh.md index 9874ead263..90030cda33 100644 --- a/packages/self-modification/README.zh.md +++ b/packages/self-modification/README.zh.md @@ -2,9 +2,11 @@ [English](README.md) | 中文 -这些面向模型的工具作用于 agent(智能体)自身所在的实时 Cordis 运行时,可检查已加载的插件和服务接口、挂载模型编写的插件,并将其 dispose(资源释放)——外加受限 repository Plugin 运行时。该组也是未来自我修改类包的落点。设计说明见[工具集 Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。 +agent 修改自身运行时:检查已加载的插件与服务接口、挂载模型编写的插件并再次 dispose,外加受限 repository Plugin 运行时。该组是未来自我修改类包的落点。设计居所:[工具集 Agent Note](../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md)。 | 包(package) | 角色 | ctx 键 | |---|---|---| | [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect`/`cordis_mount`/`cordis_unmount` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理内存中的临时插件 | 注册到 `ctx.tools` | | [`repository-plugin/`](repository-plugin/README.md) | 通过 DSH 自有子 Plugin 准备并挂载静态 repository skills 与通用 `.mcp.json` server | 注册一个 Loader builtin | + +工具契约见 [`tool-cordis/README.md`](tool-cordis/README.md) 与 [`repository-plugin/README.md`](repository-plugin/README.md)。 diff --git a/packages/session-query/README.i18n.yaml b/packages/session-query/README.i18n.yaml index 3911bf76a0..2922f4eb88 100644 --- a/packages/session-query/README.i18n.yaml +++ b/packages/session-query/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session-query/README.md -README.md: a9c9f33e2dc67e837c2be728d2b1a937ce50f13d -README.zh.md: a29dedba09271baa5836cb2d9d57c753ee41c660 +README.md: 3d5db8da74cb825c701fe50cf518505e9e6bcad9 +README.zh.md: fc430cbdc4130f77eb8857c4d88a2435ad0cc97f diff --git a/packages/session-query/README.md b/packages/session-query/README.md index a9c9f33e2d..3d5db8da74 100644 --- a/packages/session-query/README.md +++ b/packages/session-query/README.md @@ -9,3 +9,5 @@ This family provides authorized retrieval over live and durable session logs, in | [`session-query/`](session-query/README.md) | Defines trusted reads, relationship queries, and search operations | `ctx.sessionQuery` | | [`session-query-sqlite/`](session-query-sqlite/README.md) | Implements session queries with SQLite full-text search | `ctx.sessionQuery` | | [`tool-session-query/`](tool-session-query/README.md) | Exposes workspace-authorized session queries to the model | registers on `ctx.tools` | + +The subsystem reference — logical records, bounded reads, traces, filters, result pages — is [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md). diff --git a/packages/session-query/README.zh.md b/packages/session-query/README.zh.md index a29dedba09..fc430cbdc4 100644 --- a/packages/session-query/README.zh.md +++ b/packages/session-query/README.zh.md @@ -9,3 +9,5 @@ | [`session-query/`](session-query/README.md) | 定义可信读取、关系查询和搜索操作 | `ctx.sessionQuery` | | [`session-query-sqlite/`](session-query-sqlite/README.md) | 使用 SQLite 全文搜索实现会话查询 | `ctx.sessionQuery` | | [`tool-session-query/`](tool-session-query/README.md) | 向模型公开经过工作区授权的会话查询 | 注册到 `ctx.tools` | + +子系统参考——逻辑记录、有界读取、追踪、筛选器、结果页——见 [docs/subsystems/session-query.md](../../docs/subsystems/session-query.md)。 diff --git a/packages/session/README.i18n.yaml b/packages/session/README.i18n.yaml index b500a64611..3829d946f0 100644 --- a/packages/session/README.i18n.yaml +++ b/packages/session/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/session/README.md -README.md: 45eff16ad57ec1ccc8ebdd55db82f41b5adfc111 -README.zh.md: 2d67e28ef169f4e4376b2a644a3b842e5835df04 +README.md: 586d1be0286a0de935b0b08313e6965452b85376 +README.zh.md: 60e58e6d471a48d3ced03a518e96aeeae79110e8 diff --git a/packages/session/README.md b/packages/session/README.md index 45eff16ad5..586d1be028 100644 --- a/packages/session/README.md +++ b/packages/session/README.md @@ -47,3 +47,5 @@ Projects session activity into outbound telemetry and delegates delivery to a co |---|---| | [`session-telemetry/`](session-telemetry/README.md) | Defines capture, redaction, projection, and live or on-demand backend delivery. | | [`session-telemetry-otel/`](session-telemetry-otel/README.md) | Delivers telemetry through OpenTelemetry logs in `FULL`, `FEEDBACK_ONLY`, or `DISABLED` mode. | + +The subsystem references: [persistence.md](../../docs/subsystems/persistence.md), [session-projection.md](../../docs/subsystems/session-projection.md), [session-title.md](../../docs/subsystems/session-title.md), and [telemetry.md](../../docs/subsystems/telemetry.md). Only one title provider may register at a time; the demo spine mounts the fallback service and leaves both model providers out of default composition. diff --git a/packages/session/README.zh.md b/packages/session/README.zh.md index 2d67e28ef1..60e58e6d47 100644 --- a/packages/session/README.zh.md +++ b/packages/session/README.zh.md @@ -47,3 +47,5 @@ |---|---| | [`session-telemetry/`](session-telemetry/README.md) | 定义捕获、脱敏、投影,以及实时或按需后端投递。 | | [`session-telemetry-otel/`](session-telemetry-otel/README.md) | 通过 OpenTelemetry 日志以 `FULL`、`FEEDBACK_ONLY` 或 `DISABLED` 模式投递遥测。 | + +子系统参考:[persistence.md](../../docs/subsystems/persistence.md)、[session-projection.md](../../docs/subsystems/session-projection.md)、[session-title.md](../../docs/subsystems/session-title.md) 与 [telemetry.md](../../docs/subsystems/telemetry.md)。同一时间只允许一个标题提供方注册;demo 主干挂载回退服务,两个模型提供方都留在默认组合之外。 diff --git a/packages/settings/README.i18n.yaml b/packages/settings/README.i18n.yaml index ec2d2124a3..146f0ad32e 100644 --- a/packages/settings/README.i18n.yaml +++ b/packages/settings/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/settings/README.md -README.md: 3f43647f0558abf53c373ef7d97af16c0e17d2fe -README.zh.md: b5779bfe5da4cae148bcc6515ef13790f78bca7a +README.md: 6f3719c58564a90fec3bd9f305cfaeb2b7bf0bf4 +README.zh.md: 0efb16dd4e5d5faa5f16814b94d55a20dc4c06c3 diff --git a/packages/settings/README.md b/packages/settings/README.md index 3f43647f05..6f3719c585 100644 --- a/packages/settings/README.md +++ b/packages/settings/README.md @@ -8,3 +8,5 @@ This family resolves user-editable configuration through registered namespaces a |---|---|---| | [`settings/`](settings/README.md) | Defines namespace registration, layered resolution, and commits | `ctx.settings` | | [`settings-local/`](settings-local/README.md) | Stores settings in a local file and observes external edits | registers on `ctx.settings` | + +The subsystem reference — namespaces, owner scopes, resolution order, hot commits — is [docs/subsystems/settings.md](../../docs/subsystems/settings.md). diff --git a/packages/settings/README.zh.md b/packages/settings/README.zh.md index b5779bfe5d..0efb16dd4e 100644 --- a/packages/settings/README.zh.md +++ b/packages/settings/README.zh.md @@ -8,3 +8,5 @@ |---|---|---| | [`settings/`](settings/README.md) | 定义命名空间注册、分层解析与提交 | `ctx.settings` | | [`settings-local/`](settings-local/README.md) | 在本地文件中存储设置并观察外部编辑 | 注册到 `ctx.settings` | + +子系统参考——命名空间、owner scope、解析顺序、热提交——见 [docs/subsystems/settings.md](../../docs/subsystems/settings.md)。 diff --git a/packages/skill/README.i18n.yaml b/packages/skill/README.i18n.yaml index 74875f2aa3..758d0552f9 100644 --- a/packages/skill/README.i18n.yaml +++ b/packages/skill/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/skill/README.md -README.md: 533904859ad998de4f371a073fde98b68660097b -README.zh.md: 1fad581cc61a05251f671577dcb7edab37281283 +README.md: cd606e778335249bf8ecc876d3b1a4e5feb79448 +README.zh.md: 4db7e2524596f4c62144ec7eabe7c6af63e2e270 diff --git a/packages/skill/README.md b/packages/skill/README.md index 533904859a..cd606e7783 100644 --- a/packages/skill/README.md +++ b/packages/skill/README.md @@ -12,3 +12,5 @@ This family discovers reusable agent instructions and exposes them to the model | [`tool-skill/`](tool-skill/README.md) | Publishes the skill catalog and model-facing loader | registers on `ctx.tools` | This capability remains outside the core control spine and can use local, embedded, or remote providers without changing the model-facing contract. + +The subsystem reference — discovery priority, catalog snapshots, the `skill` loader — is [docs/subsystems/skills.md](../../docs/subsystems/skills.md). diff --git a/packages/skill/README.zh.md b/packages/skill/README.zh.md index 1fad581cc6..4db7e25245 100644 --- a/packages/skill/README.zh.md +++ b/packages/skill/README.zh.md @@ -12,3 +12,5 @@ | [`tool-skill/`](tool-skill/README.md) | 发布 skill 目录和面向模型的 loader | 注册到 `ctx.tools` | 该能力位于核心控制主干之外,可以使用本地、嵌入式或远程提供方,而无需更改面向模型的契约。 + +子系统参考——发现优先级、目录快照、`skill` 加载器——见 [docs/subsystems/skills.md](../../docs/subsystems/skills.md)。 diff --git a/packages/spill/README.i18n.yaml b/packages/spill/README.i18n.yaml index 95800ac03e..62ed26a59b 100644 --- a/packages/spill/README.i18n.yaml +++ b/packages/spill/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/spill/README.md -README.md: b96375b1a332cd6e3e4da9c8b1c98011aa97c8bc -README.zh.md: c8495068edf1035937c99198dfdb5db65c9a9e59 +README.md: 74fd4837cb23767754174df168f2f4ba71c2b938 +README.zh.md: 16d677719a9c3f4245a2f446017641ff6b50ffa8 diff --git a/packages/spill/README.md b/packages/spill/README.md index b96375b1a3..74fd4837cb 100644 --- a/packages/spill/README.md +++ b/packages/spill/README.md @@ -11,3 +11,5 @@ This family persists oversized tool output and replaces the inline result with a | [`spill-policy/`](spill-policy/README.md) | Applies the post-execution spill policy | listens on `ctx.tools` | See the [tool-output spill decision](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md) for the boundary between storage, retention, and tool-owned output handling. + +The subsystem reference — `SaveTextSpill`, owners/sources, the branded locator — is [docs/subsystems/spill.md](../../docs/subsystems/spill.md); rationale in the [tool output spill Agent Note](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md). diff --git a/packages/spill/README.zh.md b/packages/spill/README.zh.md index c8495068ed..16d677719a 100644 --- a/packages/spill/README.zh.md +++ b/packages/spill/README.zh.md @@ -11,3 +11,5 @@ | [`spill-policy/`](spill-policy/README.md) | 应用执行后 spill 策略 | 监听 `ctx.tools` | 参见[工具输出 spill 决策](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md),其中记录了存储、保留和工具自有输出处理之间的边界。 + +子系统参考——`SaveTextSpill`、owner/来源、品牌化定位符——见 [docs/subsystems/spill.md](../../docs/subsystems/spill.md);依据见[工具输出 spill Agent Note](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md)。 diff --git a/packages/storage/README.i18n.yaml b/packages/storage/README.i18n.yaml index 58fc19a4f8..ef32481991 100644 --- a/packages/storage/README.i18n.yaml +++ b/packages/storage/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/storage/README.md -README.md: a5f9d0204b93445699d28b1285a4a18f0408930e -README.zh.md: 0710dfb57f96011755b253ad95a0f9d2c0923a70 +README.md: a178495fca5cc433182b8e75f20d479cb7c5bc99 +README.zh.md: 1a08f1676e5c699a55f9956a42059d1f5acf7e8d diff --git a/packages/storage/README.md b/packages/storage/README.md index a5f9d0204b..a178495fca 100644 --- a/packages/storage/README.md +++ b/packages/storage/README.md @@ -12,3 +12,5 @@ This family persists application data other than session event logs through name | [`storage-domain/`](storage-domain/README.md) | Provides validated domain-record storage | `ctx.storageDomain` | Consumers use a data form rather than accessing a backend directly. The [domain storage decision](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md) records the family design. + +The subsystem reference — the backend seam, `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` — is [docs/subsystems/storage.md](../../docs/subsystems/storage.md). diff --git a/packages/storage/README.zh.md b/packages/storage/README.zh.md index 0710dfb57f..1a08f1676e 100644 --- a/packages/storage/README.zh.md +++ b/packages/storage/README.zh.md @@ -12,3 +12,5 @@ | [`storage-domain/`](storage-domain/README.md) | 提供经过验证的领域记录存储 | `ctx.storageDomain` | 消费方使用数据形式,而不是直接访问后端。[领域存储决策](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)记录了该家族的设计。 + +子系统参考——后端 seam、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed`——见 [docs/subsystems/storage.md](../../docs/subsystems/storage.md)。 diff --git a/packages/subagent/README.i18n.yaml b/packages/subagent/README.i18n.yaml index 11357c76d2..fe76e74a77 100644 --- a/packages/subagent/README.i18n.yaml +++ b/packages/subagent/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subagent/README.md -README.md: 0a342569e66539e4987710b2e56f2946c97b1ac1 -README.zh.md: 5d2f7beef478b8bfd27b4772c7a951ea62cb10ef +README.md: c8969ad313fc673456d2740b40d90c6d8db794ac +README.zh.md: fb18afba5d8680408f0f37ab75056a3fb0ef660a diff --git a/packages/subagent/README.md b/packages/subagent/README.md index 0a342569e6..c8969ad313 100644 --- a/packages/subagent/README.md +++ b/packages/subagent/README.md @@ -19,3 +19,5 @@ This family lets an agent delegate work to child agents. Multiple named provider | [`tool-subagent-report/`](tool-subagent-report/README.md) | Provides the child-to-parent report channel | registers in child scopes | See the decisions for the [capability family](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [continuable children](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), and [control tools](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md). + +The subsystem reference — start requests, results, live runs, the provider seam, continuable background children — is [docs/subsystems/subagent.md](../../docs/subsystems/subagent.md); design rationale in the [subagent capability seam](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md), [continuable background subagents](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md), and [merged subagent control service](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md) Agent Notes. diff --git a/packages/subagent/README.zh.md b/packages/subagent/README.zh.md index 5d2f7beef4..fb18afba5d 100644 --- a/packages/subagent/README.zh.md +++ b/packages/subagent/README.zh.md @@ -19,3 +19,5 @@ | [`tool-subagent-report/`](tool-subagent-report/README.md) | 提供从子级到父级的报告通道 | 注册到子级作用域 | 参见[能力家族](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)、[可继续执行的子级](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md)和[控制工具](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md)决策。 + +子系统参考——启动请求、结果、实时运行、提供方 seam、可续跑后台子 agent——见 [docs/subsystems/subagent.md](../../docs/subsystems/subagent.md);设计依据见 [subagent 能力 seam](../../.agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md)、[可续跑后台 subagent](../../.agents/notes/implemented/feature/2026-07-21-continuable-background-subagents.md) 与[合并 subagent 控制服务](../../.agents/notes/implemented/simplification/2026-07-26-merge-subagent-control-service.md) Agent Note。 diff --git a/packages/subprocess/README.i18n.yaml b/packages/subprocess/README.i18n.yaml index 0ebb5bd4af..2088cbdb6b 100644 --- a/packages/subprocess/README.i18n.yaml +++ b/packages/subprocess/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/subprocess/README.md -README.md: f2b19436da40feb14d067e2cfc706222625680b5 -README.zh.md: 938312448dd5c0a691ed07ddc9843cf2c4445637 +README.md: 9a8628e2b6d16852895be33ae516c8b33b078502 +README.zh.md: d2c539fb1c66a914aa5f758d928fba01b5600d15 diff --git a/packages/subprocess/README.md b/packages/subprocess/README.md index f2b19436da..9a8628e2b6 100644 --- a/packages/subprocess/README.md +++ b/packages/subprocess/README.md @@ -10,3 +10,5 @@ The shared process substrate for one execution world: executable lookup, fully-s | [`subprocess-local`](subprocess-local/README.md) (`@deepseek-ai/dsh-subprocess-local`) | — | The local implementation: detached process trees, bounded collection/spill, `node-pty`, foreground/session inspection, tree signalling, and terminate-and-join disposal | The service owns process lifetime across consumer reloads; consumers own what a process means (a bash command, a future non-shell runner) and every default that shapes one. + +The subsystem reference — spawn specs, output readers, outcomes, the `DSH_*` environment — is [docs/subsystems/subprocess.md](../../docs/subsystems/subprocess.md); the seam decision in the [subprocess seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md). diff --git a/packages/subprocess/README.zh.md b/packages/subprocess/README.zh.md index 938312448d..d2c539fb1c 100644 --- a/packages/subprocess/README.zh.md +++ b/packages/subprocess/README.zh.md @@ -10,3 +10,5 @@ | [`subprocess-local`](subprocess-local/README.md)(`@deepseek-ai/dsh-subprocess-local`) | 无 | 本地实现:detached 进程树、有界收集/spill、`node-pty`、前台/会话检查、进程树信号发送,以及先终止再等待退出的资源释放 | 即使消费方重载,进程生命周期仍由服务负责管理;消费方负责定义进程的含义(一条 bash 命令、未来的非 shell 运行器),以及决定塑造该进程的每一项默认值。 + +子系统参考——spawn spec、输出读取器、结果、`DSH_*` 环境——见 [docs/subsystems/subprocess.md](../../docs/subsystems/subprocess.md);seam 决定见 [subprocess seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md)。 diff --git a/packages/support/README.i18n.yaml b/packages/support/README.i18n.yaml index 1b392a1729..824f20ae7e 100644 --- a/packages/support/README.i18n.yaml +++ b/packages/support/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/support/README.md -README.md: 15cb82d3d76a1241af8b40e1c8536292618409a9 -README.zh.md: ff8595b47f1a1220912dc77a072463adf74f7bf9 +README.md: bf0bba1ea7d21700a6743195ec966512c20e01bc +README.zh.md: 47eba4d51c66914f388ba4a5925323173d5ed7e2 diff --git a/packages/support/README.md b/packages/support/README.md index 15cb82d3d7..bf0bba1ea7 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -14,3 +14,5 @@ These packages support repository development, tests, and examples rather than p | [`llm-replay/`](llm-replay/README.md) | Replays recorded model responses for keyless tests and demos | A package moves out of `support/` when it gains a product contract and product consumers. + +The invariants contract is documented in [docs/subsystems/invariants.md](../../docs/subsystems/invariants.md). diff --git a/packages/support/README.zh.md b/packages/support/README.zh.md index ff8595b47f..47eba4d51c 100644 --- a/packages/support/README.zh.md +++ b/packages/support/README.zh.md @@ -14,3 +14,5 @@ | [`llm-replay/`](llm-replay/README.md) | 为无密钥测试和演示回放已记录的模型响应 | 当一个包获得产品契约和产品消费方时,它会移出 `support/`。 + +不变式契约记录在 [docs/subsystems/invariants.md](../../docs/subsystems/invariants.md)。 diff --git a/packages/tasks/README.i18n.yaml b/packages/tasks/README.i18n.yaml index 38cd47299c..d5df91be62 100644 --- a/packages/tasks/README.i18n.yaml +++ b/packages/tasks/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/tasks/README.md -README.md: 05ed9c439684337c45abe11f03c861a1b795b2ac -README.zh.md: 16fe6c0a2b183b719cb6aa8ac068e34d51832a2b +README.md: e0b6769cd1d28cc7159310a6ec6f5c8d94a81a24 +README.zh.md: d030081470a503fb52fd72dcc6f89f4c535dc38c diff --git a/packages/tasks/README.md b/packages/tasks/README.md index 05ed9c4396..e0b6769cd1 100644 --- a/packages/tasks/README.md +++ b/packages/tasks/README.md @@ -11,3 +11,5 @@ This family gives long-running tools one owner-isolated background-task protocol | [`tool-tasks/`](tool-tasks/README.md) | Exposes task control and completion notices to the model | registers on `ctx.tools` | See the [background-task runtime](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and [task-registry](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) decisions. + +The subsystem reference — the id scheme, the owner-fenced contract, snapshots — is [docs/subsystems/tasks.md](../../docs/subsystems/tasks.md); design in the [background-task runtime](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) and [task-registry seam](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) Agent Notes. diff --git a/packages/tasks/README.zh.md b/packages/tasks/README.zh.md index 16fe6c0a2b..d030081470 100644 --- a/packages/tasks/README.zh.md +++ b/packages/tasks/README.zh.md @@ -11,3 +11,5 @@ | [`tool-tasks/`](tool-tasks/README.md) | 向模型公开任务控制和完成通知 | 注册到 `ctx.tools` | 参见[后台任务运行时](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)和[任务注册表](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md)决策。 + +子系统参考——id 方案、拥有者围栏契约、快照——见 [docs/subsystems/tasks.md](../../docs/subsystems/tasks.md);设计见[后台任务运行时](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与[任务注册表 seam](../../.agents/notes/implemented/architecture/2026-07-26-task-registry-seam.md) Agent Note。 diff --git a/packages/todo/README.i18n.yaml b/packages/todo/README.i18n.yaml index c75e09350a..57a1538b78 100644 --- a/packages/todo/README.i18n.yaml +++ b/packages/todo/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/todo/README.md -README.md: 66abf18131ee53d87e933757c40117841e94b07f -README.zh.md: 38a6b3c22653b8e22cc0b693eb666620513a3d8c +README.md: 6c7dccbbfb007737593550cea91444f8f735cf81 +README.zh.md: 3c1c35187b5111f1fdb607d70b980be57e3e79c1 diff --git a/packages/todo/README.md b/packages/todo/README.md index 66abf18131..6c7dccbbfb 100644 --- a/packages/todo/README.md +++ b/packages/todo/README.md @@ -9,3 +9,5 @@ The model-facing todo capability. It is a single **product** package because one | [`tool-todo/`](tool-todo/README.md) | Stores and exposes the session's todo list. | (registers on `ctx.tools`) | The child README owns the tool, persistence, and rendering contract. + +The event payload is documented on [docs/subsystems/session.md](../../docs/subsystems/session.md). diff --git a/packages/todo/README.zh.md b/packages/todo/README.zh.md index 38a6b3c226..3c1c35187b 100644 --- a/packages/todo/README.zh.md +++ b/packages/todo/README.zh.md @@ -9,3 +9,5 @@ | [`tool-todo/`](tool-todo/README.md) | 存储并公开会话的 todo 列表。 | (注册到 `ctx.tools`) | 子级 README 负责工具、持久化和渲染契约。 + +事件载荷记录在 [docs/subsystems/session.md](../../docs/subsystems/session.md)。 diff --git a/packages/web/README.i18n.yaml b/packages/web/README.i18n.yaml index cd412d1a99..87177273da 100644 --- a/packages/web/README.i18n.yaml +++ b/packages/web/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/web/README.md -README.md: 65b3dde1e2f9c35c308a10ca80245064f7a0361e -README.zh.md: 43b8b7d9c53336e984d9c61fa9b612fc1733e31f +README.md: 811c8b3439a8962116df0e8b3ab4f6e282ecb3d2 +README.zh.md: e328447d5d026c0abc815b269179ba6c4cd9acfc diff --git a/packages/web/README.md b/packages/web/README.md index 65b3dde1e2..811c8b3439 100644 --- a/packages/web/README.md +++ b/packages/web/README.md @@ -14,3 +14,5 @@ This family provides provider-neutral web search and fetch operations plus the m | [`tool-web/`](tool-web/README.md) | Exposes web search and fetch to the model | registers on `ctx.tools` | The [web capability decision](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) records why search and fetch share one provider-selection service. + +The subsystem reference — search/fetch requests and results, availability, `WebError` — is [docs/subsystems/web.md](../../docs/subsystems/web.md); rationale (including deferred SSRF protection) in the [web capability seam Agent Note](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md). diff --git a/packages/web/README.zh.md b/packages/web/README.zh.md index 43b8b7d9c5..e328447d5d 100644 --- a/packages/web/README.zh.md +++ b/packages/web/README.zh.md @@ -14,3 +14,5 @@ | [`tool-web/`](tool-web/README.md) | 向模型公开 web 搜索和抓取 | 注册到 `ctx.tools` | [web 能力决策](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md)记录了搜索和抓取共用一项提供方选择服务的原因。 + +子系统参考——搜索/抓取请求与结果、可用性、`WebError`——见 [docs/subsystems/web.md](../../docs/subsystems/web.md);依据(含延后的 SSRF 防护)见 [web 能力 seam Agent Note](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md)。 diff --git a/packages/workflow/README.i18n.yaml b/packages/workflow/README.i18n.yaml index f02915a31d..05ad673c61 100644 --- a/packages/workflow/README.i18n.yaml +++ b/packages/workflow/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/workflow/README.md -README.md: 2416e26e73340a6d8624b8e77681dfc9f55fe7c5 -README.zh.md: a5c94a0a9799d78e5cca72394385b38d2fb2ba70 +README.md: 7b19cfd37f9b6870825d5e0761aed92908206b3a +README.zh.md: 33b3f26780da1bf4efcd78242b2cfb212cef4c22 diff --git a/packages/workflow/README.md b/packages/workflow/README.md index 2416e26e73..7b19cfd37f 100644 --- a/packages/workflow/README.md +++ b/packages/workflow/README.md @@ -12,3 +12,5 @@ This family runs model-authored orchestration workflows over subagents and expos | [`tool-ralph/`](tool-ralph/README.md) | Exposes the fixed fresh-agent Ralph workflow | registers on `ctx.tools` | Worker threads isolate workflow execution from the host event loop but are not a security boundary. See the [dynamic-workflow](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) and [Ralph tool](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) decisions. + +The subsystem reference — start requests, `WorkflowMeta`, results, live runs, `workflow/*` events — is [docs/subsystems/workflow.md](../../docs/subsystems/workflow.md); decisions in the [dynamic-workflows](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md) and [Ralph consumer](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Notes. diff --git a/packages/workflow/README.zh.md b/packages/workflow/README.zh.md index a5c94a0a97..33b3f26780 100644 --- a/packages/workflow/README.zh.md +++ b/packages/workflow/README.zh.md @@ -12,3 +12,5 @@ | [`tool-ralph/`](tool-ralph/README.md) | 公开使用全新 agent(智能体)的固定 Ralph 工作流 | 注册到 `ctx.tools` | worker thread 将工作流执行与宿主事件循环隔离,但不构成安全边界。参见[动态工作流](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md)和 [Ralph 工具](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md)决策。 + +子系统参考——启动请求、`WorkflowMeta`、结果、实时运行、`workflow/*` 事件——见 [docs/subsystems/workflow.md](../../docs/subsystems/workflow.md);决策见[动态工作流](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md)与 [Ralph 消费方](../../.agents/notes/implemented/feature/2026-07-19-fresh-agent-ralph-workflow-tool.md) Agent Note。 diff --git a/packages/workspace/README.i18n.yaml b/packages/workspace/README.i18n.yaml index 93dea4b3f0..c7723f6c56 100644 --- a/packages/workspace/README.i18n.yaml +++ b/packages/workspace/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/workspace/README.md -README.md: 9f6ead776c1e5d36af5b6917ad8f27725b11e6a0 -README.zh.md: aa691ec681112f4c25a6391c33eabd2711c7abfc +README.md: 7e30f26a9854031b4907ad3e7edb35da5986d5d9 +README.zh.md: 6315ccb6b3a8b86161728e8296746efd0c334a29 diff --git a/packages/workspace/README.md b/packages/workspace/README.md index 9f6ead776c..7e30f26a98 100644 --- a/packages/workspace/README.md +++ b/packages/workspace/README.md @@ -9,3 +9,5 @@ This family owns persistent workspaces: user directories with titles and ordered | [`workspace/`](workspace/README.md) | Registers workspaces and accounts for their sessions | `ctx.workspace` | The [workspace package reference](workspace/README.md) owns lifecycle, persistence, and deletion semantics. + +The subsystem reference — the entity, realpath canon, registration/resolution — is [docs/subsystems/workspace.md](../../docs/subsystems/workspace.md); storage design in the [domain KV storage Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md). diff --git a/packages/workspace/README.zh.md b/packages/workspace/README.zh.md index aa691ec681..6315ccb6b3 100644 --- a/packages/workspace/README.zh.md +++ b/packages/workspace/README.zh.md @@ -9,3 +9,5 @@ | [`workspace/`](workspace/README.md) | 注册 workspace 并记录其会话归属 | `ctx.workspace` | [workspace 包参考](workspace/README.md)负责生命周期、持久化和删除语义。 + +子系统参考——实体、realpath 规范、注册/解析——见 [docs/subsystems/workspace.md](../../docs/subsystems/workspace.md);存储设计见 [domain KV 存储 Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)。 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 120904d4b5..35fef7beaf 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -156,30 +156,30 @@ export const LINK_MAP: Readonly> = { AgentCancelCause: 'core.md', AgentOptions: 'core.md', AgentStatus: 'core.md', - ContentBlock: 'core.md', + ContentBlock: 'llm-streaming.md', ContinuationDecision: 'core.md', ContinuationStop: 'core.md', - GenerateOptions: 'core.md', + GenerateOptions: 'llm-streaming.md', InboxItem: 'core.md', InboxPlacement: 'core.md', - MessageId: 'core.md', + MessageId: 'llm-streaming.md', HookContext: 'core.md', SettleReason: 'core.md', - AdapterRegistrationHandle: 'core.md', - DirectoryRegistrationHandle: 'core.md', - LlmCallConfig: 'core.md', - LlmModelContext: 'core.md', - LlmModelReasoningInfo: 'core.md', - LlmResolvedModelInfo: 'core.md', + AdapterRegistrationHandle: 'llm-streaming.md', + DirectoryRegistrationHandle: 'llm-streaming.md', + LlmCallConfig: 'llm-streaming.md', + LlmModelContext: 'llm-streaming.md', + LlmModelReasoningInfo: 'llm-streaming.md', + LlmResolvedModelInfo: 'llm-streaming.md', LlmFailure: 'llm-streaming.md', - LlmModelInfo: 'core.md', - LlmProviderInfo: 'core.md', - LlmConfigurableProvider: 'core.md', - LlmModelDiscoveryRequest: 'core.md', - LlmDiscoveredModel: 'core.md', + LlmModelInfo: 'llm-streaming.md', + LlmProviderInfo: 'llm-streaming.md', + LlmConfigurableProvider: 'llm-streaming.md', + LlmModelDiscoveryRequest: 'llm-streaming.md', + LlmDiscoveredModel: 'llm-streaming.md', ResolvedRetryPolicy: 'llm-streaming.md', - Message: 'core.md', - MessageSource: 'core.md', + Message: 'llm-streaming.md', + MessageSource: 'llm-streaming.md', UserMessage: 'session.md', PreStepDecision: 'core.md', PreStepContext: 'core.md', diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index 317ddec122..b3bf8eaec4 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -7,22 +7,17 @@ "source": "packages/util/brand/src/index.ts" }, { - "doc": "docs/subsystems/core.md", - "symbol": "ContentBlockMap", - "source": "packages/llm/llm/src/types.ts" - }, - { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "AssistantProvenance", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "Message", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "MessageSourceMap", "source": "packages/llm/llm/src/message.ts" }, @@ -42,22 +37,22 @@ "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "FinishReasonMap", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "AdapterRegistrationHandle", "source": "packages/llm/llm/src/index.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmProviderInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelInfo", "source": "packages/llm/llm/src/types.ts" }, @@ -72,47 +67,47 @@ "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelContext", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ReasoningEffortId", "source": "packages/llm/llm/src/brand.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmReasoningEffortInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelReasoningInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmResolvedModelInfo", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "GenerateOptions", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ToolSchema", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmCallConfig", "source": "packages/llm/llm/src/call-config.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmCallConfigAdapterDefaults", "source": "packages/llm/llm/src/call-config.ts" }, @@ -1491,7 +1486,7 @@ "source": "packages/settings/settings/src/index.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmConfigurableProvider", "source": "packages/llm/llm/src/types.ts" }, diff --git a/website/docs.ts b/website/docs.ts index 9a28aeea4d..1f7fa316a5 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -259,7 +259,7 @@ const cordisPrimerReference = pairedPages([ const subsystemsReference = pairedPages(([ ['README.md', '子系统', 'Subsystems', 0], - ['core.md', '核心数据结构', 'Core data structures', 1], + ['core.md', '核心', 'Core', 1], ['scope.md', '作用域', 'Scopes', 2], ['session.md', '会话', 'Sessions', 3], ['session-query.md', '会话查询', 'Session query', 4], From a2aa567371026f23ad1d20236f19c8e97be8cf29 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:34:00 +0800 Subject: [PATCH 19/19] docs(subsystems): open core.md on agent creation/ownership and the Agent contract; enforce a complete folder index core.md claimed to be the packages/core reference but opened on repo-wide type patterns and never documented the ownership vocabulary: AgentHandle, CreateAgentOptions, ResumeAgentOptions, and AgentFactory were TYPE_LINK_EXEMPTIONS pointing at a package README, invisible to the folder that calls itself the type reference. The page now reads spine map -> creation and ownership (AgentHandle pasted; the options and factory summarized with links into the generated registry section) -> the Agent handle (AgentStatus, AgentOptions, SteeringOutcome, SteeringReceipt, and SettleReason now pasted; the one settlement prose wall split by topic; delivery vocabulary ordered as a message travels) -> initiator -> interception -> a Sessions summary -> the ToolDefinition pointer -> an explicitly framed repo-wide patterns tail (the ...Map pattern, branded ids). The duplicate SessionEvent paste is gone -- session.md owns it and LINK_MAP follows -- the four ownership types moved from TYPE_LINK_EXEMPTIONS into LINK_MAP -> core.md, and three dead LINK_MAP entries (ContinuationDecision, ContinuationStop, HookContext) no longer name types absent from the source tree. The "what this page owns" meta-section folds into the intro. The subsystems README index silently lost tasks.md and session-reference.md on both language sides during a base absorption; the rows are restored and scripts/project-doc-site.spec.ts now fails when any page misses either side of the index (proven red on a removed row). tools.md links ToolSchema to its llm-streaming.md declaration instead of calling it core; subagent.md links AgentHandle and CreateAgentOptions.seed to the new section. A new Agent Note records the package-anchored page-scoping decision; the 2026-06-20 catalog note marks its spine-vs-seam rule superseded as the page-scoping rule while keeping the type-equiv mechanism current, and docs/AGENTS.md cites the new note. --- ...-20-core-data-structures-catalog.i18n.yaml | 4 +- ...2026-06-20-core-data-structures-catalog.md | 2 + ...6-06-20-core-data-structures-catalog.zh.md | 4 +- ...package-anchored-subsystem-pages.i18n.yaml | 6 + ...-08-03-package-anchored-subsystem-pages.md | 34 + ...-03-package-anchored-subsystem-pages.zh.md | 34 + docs/AGENTS.md | 6 +- docs/subsystems/README.i18n.yaml | 4 +- docs/subsystems/README.md | 7 +- docs/subsystems/README.zh.md | 7 +- docs/subsystems/core.i18n.yaml | 4 +- docs/subsystems/core.md | 692 ++++++--------- docs/subsystems/core.zh.md | 832 +++++++----------- docs/subsystems/http-server.i18n.yaml | 2 +- docs/subsystems/http-server.zh.md | 2 +- docs/subsystems/llm-streaming.i18n.yaml | 4 +- docs/subsystems/llm-streaming.md | 193 +++- docs/subsystems/llm-streaming.zh.md | 193 +++- docs/subsystems/permission.i18n.yaml | 4 +- docs/subsystems/permission.md | 2 +- docs/subsystems/permission.zh.md | 2 +- docs/subsystems/persistence.i18n.yaml | 4 +- docs/subsystems/persistence.md | 2 +- docs/subsystems/persistence.zh.md | 2 +- docs/subsystems/session-projection.i18n.yaml | 4 +- docs/subsystems/session-projection.md | 2 +- docs/subsystems/session-projection.zh.md | 2 +- docs/subsystems/session.i18n.yaml | 4 +- docs/subsystems/session.md | 2 +- docs/subsystems/session.zh.md | 2 +- docs/subsystems/subagent.i18n.yaml | 4 +- docs/subsystems/subagent.md | 4 +- docs/subsystems/subagent.zh.md | 4 +- docs/subsystems/tools.i18n.yaml | 4 +- docs/subsystems/tools.md | 2 +- docs/subsystems/tools.zh.md | 2 +- packages/interaction/README.i18n.yaml | 4 +- packages/interaction/README.md | 2 +- packages/interaction/README.zh.md | 2 +- packages/self-modification/README.i18n.yaml | 2 +- packages/self-modification/README.zh.md | 2 - scripts/gen-cordis-catalog.ts | 13 +- scripts/project-doc-site.spec.ts | 17 +- .../request-response.expected.json | 4 +- scripts/type-equiv.manifest.json | 30 +- website/docs.ts | 2 +- 46 files changed, 1126 insertions(+), 1034 deletions(-) create mode 100644 .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml create mode 100644 .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md create mode 100644 .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml index 1b1a93fc99..def34cdfbd 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md -2026-06-20-core-data-structures-catalog.md: b358ad54652e5b16ae90037c128cf756bcfdaed3 -2026-06-20-core-data-structures-catalog.zh.md: d84c2befaee4c2032b358ed1a7949ac0151ae74c +2026-06-20-core-data-structures-catalog.md: 9a3f80df592f28adc365d5e64cc15516d92b47b5 +2026-06-20-core-data-structures-catalog.zh.md: 96c606a3d65a67e757960c5cbc997e717f11e8d5 diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md index b358ad5465..9a3f80df59 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md @@ -16,6 +16,8 @@ A new `docs/subsystems/` folder catalogs the vocabulary, with a new `verify-type ### What counts as "core" — the spine-vs-seam line +> **Superseded as the page-scoping rule** by [package-anchored subsystem pages](2026-08-03-package-anchored-subsystem-pages.md): each page now anchors to the package group that declares its vocabulary. The `ts type-equiv` mechanism below remains current. + The scoping line was not picked top-down; it was discovered by testing candidate definitions against concrete borderline types until one rule survived every case. The decisive test was `BashExecRequest`/`BashExecSpec`/`BashRunResult`: bash is a capability *seam*, not part of the agent-loop spine, so if those are "core" then "core" means *all cross-package vocabulary* and the catalog is a flat dump; if they are not, "core" means *the central spine* and bash vocabulary belongs on its own seam page. The latter won, which set the whole structure: a **tiered folder**, not a flat document. The rule that settled the remaining cases: ***the type you write, hold, or receive is core; the machinery that types it, renders it, or persists it is a seam-page detail.*** Worked through: diff --git a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md index d84c2befae..96c606a3d6 100644 --- a/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md +++ b/.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.zh.md @@ -16,6 +16,8 @@ Status: implemented ### 何为「核心」——主干与 seam 的分界线 +> **作为页面范围界定规则已被取代**,见[按包锚定的子系统页面](2026-08-03-package-anchored-subsystem-pages.md):每页现在锚定到声明其词汇的包分组。下文的 `ts type-equiv` 机制仍然有效。 + 范围界定并非自上而下拍定,而是将候选定义逐一对照具体的边界类型反复测试,直到一条规则在所有案例中都成立。决定性的测试是 `BashExecRequest`/`BashExecSpec`/`BashRunResult`:bash 是一个能力 *seam*,不属于 agent loop(智能体循环)主干;如果这些算「核心」,那么「核心」就意味着*所有跨包词汇*,目录沦为平铺罗列;如果不算,「核心」就意味着*中央主干*,bash 词汇归入子页面。后者胜出,由此确定了整体结构:一个**分层文件夹**,而非一份平铺文档。 确定其余案例的规则是:***你编写、持有或接收的类型是核心;为其提供类型推导、渲染或持久化的机制是子页面细节。*** 逐一验证如下: @@ -25,7 +27,7 @@ Status: implemented - `ToolSchema` 是核心(它是流经每个步骤的模型请求 `GenerateOptions` 的一个字段),即使它在概念上属于工具流水线——当*流经主干*与*概念归属*冲突时,前者胜出。 - 工具展示词汇(`ToolCallView`/`ToolResultView` 等)、`SessionPersistence` 持久性 seam 以及 bash 词汇是子页面。 -`core.md` 是一份**自包含的主干文档**:它给出每个主干结构的确切类型定义,辅以最少的行文,并链接到子页面获取各 seam 的细节。子页面包括 `llm-streaming.md`、`session.md`、`persistence.md`(沿内存模型与持久性 seam 的分界线从会话页面拆出)、`tools.md` 和 `bash.md`。 +`core.md` 是一份**自包含的主干文档**:它给出每个主干结构的确切类型定义,辅以最少的行文,并链接到同级 seam 页面获取各 seam 的细节;目录的 [README](../../../../docs/subsystems/README.md) 索引全部页面。最初的 seam 页面包括 `llm-streaming.md`、`session.md`、`persistence.md`(沿内存模型与持久性 seam 的分界线从会话页面拆出)、`tools.md` 和 `bash.md`。 ### `ts type-equiv` 机制——既逐字又防漂移 diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml new file mode 100644 index 0000000000..7bc08eb82d --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md +2026-08-03-package-anchored-subsystem-pages.md: 5ba7937f0c142727d343a107a3ff87f5f337931e +2026-08-03-package-anchored-subsystem-pages.zh.md: 27d56158a69a8144b0915054032fb3b54493bd0b diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md new file mode 100644 index 0000000000..5ba7937f0c --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md @@ -0,0 +1,34 @@ +# Agent Note: Package-anchored subsystem pages and thin group READMEs + +Status: implemented + +English | [中文](2026-08-03-package-anchored-subsystem-pages.zh.md) + +## Problem + +The [subsystems catalog](2026-06-20-core-data-structures-catalog.md) scoped its front page by the spine-vs-seam rule: a type was "core" if the loop holds, derives, streams, or logs it on every turn. That rule selected types, not packages, so as the folder grew to forty-plus pages the front page became a cross-package grab-bag: LLM conversation vocabulary sat above the agent contracts, the creation/ownership vocabulary (`AgentHandle`, `CreateAgentOptions`, `ResumeAgentOptions`, `AgentFactory`) was documented nowhere in the folder because the generator exempted it to a package README, and a reader could not predict which page documents a type from where the type lives. Package-group READMEs meanwhile had no common shape — some carried sectioned tables, stray design essays, or trailing paragraphs that belonged on a subsystem page. + +## Decision + +Every `docs/subsystems/` page anchors to the package or package group that declares its vocabulary, and page membership follows the repository layout: [core.md](../../../../docs/subsystems/core.md) is the `packages/core` page (creation and ownership, the `Agent` handle with its delivery/cancellation/interception contracts, pointers to the group's dedicated pages), [llm-streaming.md](../../../../docs/subsystems/llm-streaming.md) owns `packages/llm` end-to-end, and so on. Repo-wide type patterns (`…Map → derived-union`, branded ids) stay on core.md in an explicitly framed closing section rather than interleaved with the package content. This supersedes the spine-vs-seam rule *as the page-scoping rule*; the placement heuristic that survives is simpler: a type is documented where its declaring package's page is, and machinery keeps living with its machinery. + +Every type a generated signature references must resolve somewhere in the folder: the agent ownership vocabulary moved from the generator's `TYPE_LINK_EXEMPTIONS` into `LINK_MAP → core.md`, so exemptions are reserved for genuinely service-local or vendored shapes. Each pasted declaration has one home (`SessionEvent` lives on [session.md](../../../../docs/subsystems/session.md); core.md summarizes and links). + +Every `packages//README.md` pair is a thin front door in one shape: a why-first intro paragraph, a package table (Package / Role / ctx key), and a closing pointer to the owning subsystems page. Load-bearing prose that outgrows that shape relocates to the owning subsystems page rather than being deleted. + +The [subsystems README](../../../../docs/subsystems/README.md) indexes every page in the folder on both language sides; `scripts/project-doc-site.spec.ts` enforces one table row per page, so a page added by a later PR (or absorbed in a merge) cannot silently miss the index. + +## Alternatives considered + +**Keep the spine-vs-seam scoping rule.** It answered "is this type core?" per type, which is why the front page accumulated types from four packages while missing half of `packages/core/agent`'s public surface. Predictability by repository layout won. + +**A flat single-document catalog.** Already rejected in the [original catalog note](2026-06-20-core-data-structures-catalog.md); the growth to forty-one pages confirmed that verdict. + +**Document ownership vocabulary only in package READMEs (the exemption status quo).** This left `AgentHandle` and the create/resume options invisible to the folder that claims to be the type reference, and the generated `Types:` footers could not link them. + +## Consequences + +- Which page documents a type is predictable from `packages//`; the subsystems README is a complete index enforced by test. +- Generated signature footers link the agent ownership vocabulary instead of silently exempting it. +- `verify-type-equiv`'s 1:1 manifest keeps each paste single-homed; the duplicate `SessionEvent` paste is gone. +- The [original catalog note](2026-06-20-core-data-structures-catalog.md) remains the owner of the `ts type-equiv` drift-gate mechanism; only its page-scoping rule is superseded here. diff --git a/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md new file mode 100644 index 0000000000..27d56158a6 --- /dev/null +++ b/.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.zh.md @@ -0,0 +1,34 @@ +# Agent Note:按包锚定的子系统页面与轻薄的分组 README + +Status: implemented + +[English](2026-08-03-package-anchored-subsystem-pages.md) | 中文 + +## Problem + +[子系统目录](2026-06-20-core-data-structures-catalog.md)最初用主干-vs-seam 规则界定首页范围:如果循环在每个轮次都持有、派生、流式传输或记录某个类型,它就是「核心」。该规则选择的是类型而非包,因此当目录增长到四十多页后,首页变成了跨包大杂烩:LLM 对话词汇排在 agent 契约之前,创建/所有权词汇(`AgentHandle`、`CreateAgentOptions`、`ResumeAgentOptions`、`AgentFactory`)在目录中无处记录(生成器把它们豁免给了某个包 README),读者无法根据类型所在位置预测哪一页记录它。与此同时,各包分组 README 没有统一形状——有的带分节表格、游离的设计短文,或本应属于子系统页面的尾部段落。 + +## Decision + +每个 `docs/subsystems/` 页面锚定到声明其词汇的包或包分组,页面归属跟随仓库布局:[core.md](../../../../docs/subsystems/core.md) 是 `packages/core` 的页面(创建与所有权、`Agent` 句柄及其投递/取消/拦截契约、指向该组专属页面的指针),[llm-streaming.md](../../../../docs/subsystems/llm-streaming.md) 端到端拥有 `packages/llm`,依此类推。全仓通用类型模式(`…Map → 派生联合`、品牌化 id)保留在 core.md 一个明确标注的收尾小节中,而不是与包内容交错。这在*页面范围界定规则*的意义上取代了主干-vs-seam 规则;存活下来的放置启发式更简单:类型记录在其声明包对应的页面,机制仍与其机制放在一起。 + +生成签名引用的每个类型都必须能在目录中某处解析:agent 所有权词汇从生成器的 `TYPE_LINK_EXEMPTIONS` 移入 `LINK_MAP → core.md`,因此豁免只留给真正服务本地或 vendored 的形状。每个粘贴的声明只有一个家(`SessionEvent` 位于 [session.md](../../../../docs/subsystems/session.md);core.md 概括并链接)。 + +每个 `packages//README.md` 配对都是统一形状的轻薄门面:一段以「为什么」开头的介绍、一张包表格(包 / 角色 / ctx 键)、一个指向拥有方子系统页面的收尾指针。超出该形状的承重散文迁移到拥有方子系统页面,而非删除。 + +[子系统 README](../../../../docs/subsystems/README.md) 在两个语言侧索引目录中的每一页;`scripts/project-doc-site.spec.ts` 强制每页一行表格,因此后续 PR 新增(或合并吸收)的页面无法悄悄缺席索引。 + +## Alternatives considered + +**保留主干-vs-seam 界定规则。**它逐类型回答「这个类型是核心吗?」,这正是首页积累了四个包的类型、却缺失 `packages/core/agent` 一半公开 surface 的原因。按仓库布局可预测性胜出。 + +**扁平的单文档目录。**在[原目录 note](2026-06-20-core-data-structures-catalog.md) 中已被否决;增长到四十一页证实了该结论。 + +**只在包 README 中记录所有权词汇(豁免的现状)。**这让 `AgentHandle` 与 create/resume 选项在自称类型参考的目录中不可见,生成的 `Types:` 页脚也无法链接它们。 + +## Consequences + +- 哪一页记录某类型可由 `packages//` 预测;子系统 README 是由测试强制的完整索引。 +- 生成的签名页脚链接 agent 所有权词汇,而不是静默豁免。 +- `verify-type-equiv` 的 1:1 manifest 保证每个粘贴单一归属;重复的 `SessionEvent` 粘贴已移除。 +- [原目录 note](2026-06-20-core-data-structures-catalog.md) 仍拥有 `ts type-equiv` 漂移检查机制;此处仅取代其页面范围界定规则。 diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 88c90678de..a5eca656db 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -21,8 +21,8 @@ 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 | | 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 (→ subsystems), per-package detail (→ package READMEs), decision rationale (→ Agent Notes), implementation-status annotations | -| [subsystems/](subsystems/README.md) | One reference page per subsystem: literal type shapes, semantics, and the generated Cordis surface | Behavior narration (→ architecture.md) | -| [Agent Notes](../.agents/notes/README.md) | Active decision records: the why, what-was-given-up, and concise verification contract; `implemented/` notes describe shipped reality in present tense | Migration plans, acceptance-task checklists, fixture walkthroughs, and spec-speak ("should…") once the decision has shipped; archived notes are frozen history, never current authority | +| [subsystems/](subsystems/README.md) | One reference page per subsystem: type shapes, semantics, and the generated Cordis surface | Behavior narration (→ architecture.md) | +| [Agent Notes](../.agents/notes/README.md) | Active decision records: the why, what-was-given-up, and verification contract; `implemented/` notes describe shipped reality in present tense | Migration plans, acceptance-task checklists, fixture walkthroughs, and spec-speak ("should…") once the decision has shipped; archived notes are frozen history, never current authority | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the Agent Note each guide links) | | [user/](user/index.md) | Product-facing guides published by the documentation website | Generated reference tables, contributor procedures, decision history | @@ -39,7 +39,7 @@ Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookb - **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. - **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 owning [subsystems page](subsystems/README.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 owning [subsystems page](subsystems/README.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types; a type is documented on its declaring package group's page ([page scoping](../.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.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. - Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams". diff --git a/docs/subsystems/README.i18n.yaml b/docs/subsystems/README.i18n.yaml index 3303bcb935..f1636e0191 100644 --- a/docs/subsystems/README.i18n.yaml +++ b/docs/subsystems/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/README.md -README.md: d225e5f4eeb31d1e6c71cbd8311e28bae1e8b3a5 -README.zh.md: 9f78ac3e1c79440b053ee4e344dbdba2c11b3dfc +README.md: 1c3d14aeb4fd11cbc45eaadd83f0b84de0dfda52 +README.zh.md: dda61d86c4d00117af851b4a55d16b97c6bc2f10 diff --git a/docs/subsystems/README.md b/docs/subsystems/README.md index d225e5f4ee..1c3d14aeb4 100644 --- a/docs/subsystems/README.md +++ b/docs/subsystems/README.md @@ -6,7 +6,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | Page | Owns | |---|---| -| [core.md](core.md) | the `packages/core` control spine: the package-by-package loop map, the `Agent` handle with its delivery/cancellation/interception contracts, the `SessionEvent` envelope, branded ids, the `…Map → derived-union` pattern | +| [core.md](core.md) | the `packages/core` control spine: the package-by-package loop map, agent creation and ownership (`AgentHandle`), the `Agent` handle with its delivery/cancellation/interception contracts, and the repo-wide type patterns (`…Map → derived-union`, branded ids) | | [llm-streaming.md](llm-streaming.md) | the `packages/llm` conversation vocabulary — `Message`/`ContentBlock`, the assembled model request, the `StreamChunk` wire protocol + adapter contract, `BlockAssembler`, the `LlmAdapter` seam | | [token-meter.md](token-meter.md) | immutable scalar and positional replay measurements with consumed-log revisions | | [scope.md](scope.md) | scoped registration identity, dispatch carriers, and the owned `Scope` context | @@ -19,6 +19,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [credentials.md](credentials.md) | the credential seam: `CredentialRef` references (never values) in configuration, per-operation resolution, UI-safe `CredentialInfo`, provider source layers | | [session-query.md](session-query.md) | logical records, bounded exact-event reads, relationship traces, semantic filters/documents, and full-text result pages | | [session-title.md](session-title.md) | durable title snapshots, source provenance, and the asynchronous provider contract | +| [session-reference.md](session-reference.md) | structured cross-session references: `SessionReferenceInput`/`Candidate`, prepared message contexts, the stable error taxonomy | | [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly | | [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, and the guarded execution pipeline | | [user-interaction.md](user-interaction.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy | @@ -36,12 +37,12 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures | [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` | | [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` | | [workflow.md](workflow.md) | the workflow seam: `WorkflowStartRequest`, `WorkflowMeta`, `WorkflowRun`/`Result`, the `workflow/*` event payloads, `WorkflowError` fatality | +| [tasks.md](tasks.md) | the background-task runtime: branded `TaskId`s, the producer contract, consumer views, `ctx.tasks` service behavior | | [permission.md](permission.md) | the permission-preset layer: `PresetSpec`/`PresetOption`, the derived `custom` state, the log-only `permission/preset` event | | [plan.md](plan.md) | plan mode: the log-only `plan/mode` state, pending-selection flush, `PlanModeConfig`, the `exit_plan_mode` review arc | | [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 claimable fallback seat, index taps | | [storage.md](storage.md) | the storage subsystem: the backend seam (`StorageBackend`), `StorageForms`, `DomainSpec`/`Domain`, `domain/changed` | -| [tui.md](tui.md) | the terminal-extension seam: `TuiOverlayRequest`/`Host`/`Session`, close reasons and outcomes, the modal queue | | [workspace.md](workspace.md) | the workspace registry: `Workspace`/`WorkspaceId`, registration and resolution, the session `cwd` relationship | | [client-modules.md](client-modules.md) | the web plugin table: `dshClient` declarations, `WebBootGraph` wire composition, the bundle route and index tap | | [session-projection.md](session-projection.md) | the projection seam: `SessionProjectionMap`, the pure `ProjectionDefinition` unit, `ProjectionSnapshot`'s consistent cut, the change feed | diff --git a/docs/subsystems/README.zh.md b/docs/subsystems/README.zh.md index 9f78ac3e1c..dda61d86c4 100644 --- a/docs/subsystems/README.zh.md +++ b/docs/subsystems/README.zh.md @@ -6,7 +6,7 @@ | 页面 | 负责内容 | |---|---| -| [core.md](core.md) | `packages/core` 控制主干:逐包循环地图、`Agent` 句柄及其投递/取消/拦截契约、`SessionEvent` 信封、品牌化 id,以及 `…Map → 派生联合` 模式 | +| [core.md](core.md) | `packages/core` 控制主干:逐包循环地图、agent 创建与所有权(`AgentHandle`)、`Agent` 句柄及其投递/取消/拦截契约,以及全仓通用类型模式(`…Map → 派生联合`、品牌化 id) | | [llm-streaming.md](llm-streaming.md) | `packages/llm` 的对话词汇——`Message`/`ContentBlock`、组装完成的模型请求、`StreamChunk` 协议格式(wire format)+ 适配器契约(adapter contract)、`BlockAssembler`、`LlmAdapter` seam | | [token-meter.md](token-meter.md) | 不可变的标量与位置回放度量,附带已消费日志修订号 | | [scope.md](scope.md) | 作用域注册标识、dispatch 载体,以及拥有的 `Scope` 上下文 | @@ -19,6 +19,7 @@ | [credentials.md](credentials.md) | 凭据 seam:配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、provider 来源层 | | [session-query.md](session-query.md) | 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页 | | [session-title.md](session-title.md) | 持久标题快照、来源 provenance 与异步提供方契约 | +| [session-reference.md](session-reference.md) | 结构化跨会话引用:`SessionReferenceInput`/`Candidate`、prepared 消息上下文、稳定错误分类 | | [system-prompt.md](system-prompt.md) | 逐次组装的上下文、工具提供方结果、提示词段落与协作式组装 | | [tools.md](tools.md) | `ToolDefinition` 完整字段、schema DSL、`ToolExecution`/`ToolResult`、工具展示 UI 类型,以及受保护的执行流水线 | | [user-interaction.md](user-interaction.md) | UI 支持的人工问答 seam:`AskUserQuestionRequest`、answer/options 词汇、提供方 API、错误分类体系 | @@ -36,12 +37,12 @@ | [web.md](web.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` | | [spill.md](spill.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` | | [workflow.md](workflow.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 | +| [tasks.md](tasks.md) | 后台任务运行时:品牌化 `TaskId`、producer 契约、consumer 视图、`ctx.tasks` 服务行为 | | [permission.md](permission.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 | | [plan.md](plan.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 | | [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`、匹配顺序、可认领的回退席位、index 转换 | | [storage.md](storage.md) | 存储子系统:后端 seam(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` | -| [tui.md](tui.md) | 终端扩展 seam:`TuiOverlayRequest`/`Host`/`Session`、关闭原因与结果、模态队列 | | [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 | | [client-modules.md](client-modules.md) | Web 插件表:`dshClient` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 | | [session-projection.md](session-projection.md) | 投影 seam:`SessionProjectionMap`、纯函数 `ProjectionDefinition` 单元、`ProjectionSnapshot` 的一致切面、变更馈送 | diff --git a/docs/subsystems/core.i18n.yaml b/docs/subsystems/core.i18n.yaml index 16c8e29cf2..caf9356ee9 100644 --- a/docs/subsystems/core.i18n.yaml +++ b/docs/subsystems/core.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/core.md -core.md: ba9319d377cf250148a145ba3f1fa1baa967c858 -core.zh.md: 276dc4f99f1f802e66aca050b2fbcdc178931611 +core.md: cc9cc30677b0b657492d8a5118489e8b801f3fd3 +core.zh.md: 6878300dc2cbd82d220e17d2d6f5e1009f41c48f diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index ba9319d377..cc9cc30677 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -2,11 +2,11 @@ English | [中文](core.zh.md) -The **core** subsystem is [`packages/core`](../../packages/core/README.md) — the control spine every composition boots: the event-sourced session log, system-prompt assembly, the tool registry, the agent vocabulary, and the concrete loop that drives them. This page owns the spine's shared vocabulary — the `Agent` handle, its delivery and interception contracts, and the repo-wide type patterns — and orients to the group's dedicated pages; the folder is indexed in the [subsystems README](README.md). +The **core** subsystem is [`packages/core`](../../packages/core/README.md) — the control spine every composition boots: the event-sourced session log, system-prompt assembly, the tool registry, the agent vocabulary, and the concrete loop that drives them. This page owns what the `agent`/`agent-loop` pair declares — how an agent is created and owned, and the `Agent` handle with its delivery, cancellation, and interception contracts — plus the two type patterns every subsystem follows; the group's dedicated pages and the rest of the folder are indexed in the [subsystems README](README.md). ## The spine, package by package -A turn flows through the six packages in one loop: the driver in [`agent-loop`](../../packages/core/agent-loop) claims a queued prompt, opens a turn on the [session log](session.md) (`ctx.sessions`), assembles the request prefix through [system-prompt](system-prompt.md) (`ctx.systemPrompt`) and derives history from the log, streams the model response through the [LLM seam](llm-streaming.md), dispatches tool calls through the [tool registry](tools.md) (`ctx.tools`), and appends every model-visible fact back onto the log before the next step derives from it. +A turn flows through the six packages in one loop: the driver in [`agent-loop`](../../packages/core/agent-loop) claims a queued prompt, opens a turn on the [session log](session.md) (`ctx.sessions`), assembles the request prefix through [system-prompt](system-prompt.md) (`ctx.systemPrompt`) and derives history from the log, streams the model response through the [LLM seam](llm-streaming.md), dispatches tool calls through the [tool registry](tools.md) (`ctx.tools`), and appends every model-visible fact back onto the log before the next step derives from it. The conversation vocabulary the loop moves — `Message`, `ContentBlock`, `StreamChunk`, the model request — is declared by [`packages/llm`](../../packages/llm/README.md) and documented on [llm-streaming.md](llm-streaming.md). | Package | Owns | Page | |---|---|---| @@ -19,208 +19,45 @@ A turn flows through the six packages in one loop: the driver in [`agent-loop`]( `scope/` is the one non-service package: a dependency-free library (`createScope`/`scopeOf`/`scopeTarget`) that sits below `session/` and `system-prompt/` in the module graph precisely so they can consume it without a cycle. `agent-loop` is the one concrete implementation of the `agent` seam and lives here because it is the harness's default product loop; it runs each driver inside `ctx.agents.withInitiator()`. Extension plugins depend on `agent` — including when they need the initiating Agent — and never on `agent-loop` directly, so the loop stays swappable. The default composition that wires this spine into a runnable agent is [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md). - +## Creation and ownership -## What this page owns +Consumers create agents through `ctx.agents` — `create()` builds a fresh session and agent under one caller-supplied `SessionId`, `resume()` loads a persisted session first — or declaratively through the loop's config entries. Programmatic creation returns the owner's handle: -The conversation vocabulary the loop moves — `Message`, `ContentBlock`, `StreamChunk`, the model request — is declared by [`packages/llm`](../../packages/llm/README.md) and documented on [llm-streaming.md](llm-streaming.md); the session-event, prompt-assembly, and tool vocabularies live on this group's dedicated pages above. What remains here is the vocabulary shared by everything: the `Agent` handle and its delivery, cancellation, and interception contracts (declared by `packages/core/agent`), the `SessionEvent` envelope, and the two type patterns every subsystem follows. The scoping rule is recorded in the [subsystems-catalog Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md): the type you write, hold, or receive is documented where its declaring subsystem is; the machinery that types, renders, or persists it stays on that machinery's page. - -## The `…Map → derived-union` pattern - -Almost every extensible sum type in the harness follows one shape: an interface keyed by a discriminant tag (the `…Map`), from which the union is derived with `keyof`. Plugins add variants by **declaration merging** — no edit to the owning package. - -```ts ignore-check -// The pattern, schematically: -interface ThingMap { - 'a': { kind: 'a'; /* … */ } - 'b': { kind: 'b'; /* … */ } -} -type ThingKind = keyof ThingMap // 'a' | 'b' -type Thing = ThingMap[keyof ThingMap] // the discriminated union - -// A plugin extends it without touching the source package: -declare module '@deepseek-ai/dsh-llm' { - interface ThingMap { - 'c': { kind: 'c'; /* … */ } - } -} -``` - -Six canonical maps use this pattern; a plugin author extends these: - -| Map | Package | Derives | Catalog | -|---|---|---|---| -| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | -| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | -| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | -| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | -| `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | -| `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | - -Two large discriminated unions are the ones consumers `switch` over most: **`StreamChunk`** (the streaming protocol) and **`SessionEvent`** (the log entry). Per the repo convention, `switch` on the tag — don't chain `if`s — so each arm narrows and a typo'd tag fails to compile. - -## Branded IDs - -IDs that cross package boundaries are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `CallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings. - -The `Branded` primitive lives in its own type-only package, [dsh-brand](../../packages/util/brand) (no runtime code, no harness-package dependency), so any package can brand the ids it owns without depending on an unrelated capability package. - -Source: [`packages/util/brand/src/index.ts`](../../packages/util/brand/src/index.ts) - -```ts type-equiv -/** A string carrying a compile-time-only brand `B`. */ -type Branded = string & { readonly [BRAND]: B } -``` - -The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `TaskId` in [tasks.md](tasks.md). - -## Sessions - -A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. The event vocabulary derives from `SessionEventMap`: - -Source: [`packages/core/session/src/types.ts`](../../packages/core/session/src/types.ts) +Source: [`packages/core/agent/src/index.ts`](../../packages/core/agent/src/index.ts) ```ts type-equiv /** - * One immutable entry in the session log. + * An owned agent plus its disposer, returned by {@link AgentRegistry.create} / + * {@link AgentRegistry.resume}. The disposer is a CAPABILITY: among consumers, + * only the holder can tear this agent down. The registered factory provider is + * also a structural owner because the scoped agent depends on that provider's + * service surface; provider unload stops and drains every live handle it made. + * `dispose()` stops the loop, awaits its exit, unregisters the agent, removes + * its session from the store, and finally unwinds its scoped world. * - * A proper discriminated union over `type` (not independent `type`/`data` - * unions), so `switch (event.type)` narrows `event.data` without casts. - * - * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: - * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`, `steering/message`). - * Non-surface events (boundary markers, chunks, usage, errors) never carry - * surface metadata — the compiler enforces this at `Session.append()` - * call sites. + * `ctx.agents.get(id)` still returns a bare {@link Agent} — the handle is + * exposed only to the consumer owner that created it; the structural provider + * reaches the same teardown internally. Config-created agents (the loop's own + * startup) are owned by the loop fiber and never need a handle. */ -type SessionEvent = { - [K in SessionEventType]: { - type: K - /** Monotonic sequence number within the session. */ - seq: number - /** Unix epoch milliseconds. */ - time: number - data: SessionEventMap[K] - } & (K extends SurfaceEventType ? { - /** - * Seq numbers of events that are provenance sources of this event - * (e.g. the `assistant/chunk` seqs that built an `assistant/message`, - * or the surface nodes shadowed by a compaction replace node). An - * `assistant/message` may carry a present empty array for a known empty - * provider stream; omission means unrecorded provenance. - */ - sourceEventSeqs?: number[] - /** How this event entered the surface; absent for non-surface events. */ - surfaceOp?: SurfaceOp - } : object) -}[T] +interface AgentHandle { + agent: Agent + dispose(): Promise +} ``` -The twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. +`CreateAgentOptions` carries the shared identity and everything a fresh agent needs before publication: session metadata (`meta` — validated `cwd`, fork lineage, seed boundary, origin classification, delegation depth), an optional `seed` replay prefix for forks, per-agent `AgentOptions`, a creation-only cancellation `signal`, and `setup`. `ResumeAgentOptions` is the persisted-identity counterpart: `resumeSessionId`, `agentOptions`, `signal`, and `setup`. The `setup` callback (`AgentSetup`) composes the agent's scoped world while both ids are still unpublished — everything registered through `agentCtx` exists before `agent/created` and the first prompt assembly — and may return a synchronous commit invoked immediately before publication; a setup rejection, commit throw, or owner disposal rolls the transaction back without publishing either id. + +`AgentFactory` is the creation seam behind the registry: the loop registers its factory via `ctx.agents.setFactory()`, so consumers program against `ctx.agents` without depending on the concrete loop package. The exact `create`/`resume` signatures and their rollback contracts are in the [generated section](#ctxagents--agentregistry) below. ## The agent handle -`Agent` is the surface every plugin (UI, hooks, orchestrators) programs against. The concrete implementation is package-internal to dsh-agent-loop; nothing outside the loop depends on it. +`Agent` is the surface every plugin (UI, hooks, orchestrators) programs against; `ctx.agents.get(id)` returns it, and the [initiator scope](#initiating-agent) carries it. The concrete implementation is package-internal to dsh-agent-loop; nothing outside the loop depends on it. The unified `send` method exposes target and wakeup routing directly; `followup`, `steer`, and `inject` are fixed-preset aliases. Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) ```ts type-equiv -/** - * Which inbox queue a {@link Agent.send} item joins: - * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. - * - `next-step` — during prompt admission or an open turn, the item stages for - * the next safe step boundary; otherwise it is promoted per its `wakeup` - * flag. - */ -type SendTarget = 'next-turn' | 'next-step' -``` - -```ts type-equiv -/** Resolved inbox placement reported when an accepted message is enqueued. */ -type InboxPlacement = 'queued' | 'steering' -``` - -`InboxItemId` is a process-local branded string minted for each accepted FIFO occurrence. It is intentionally distinct from `MessageId`: sending the same immutable message twice creates two independently addressable pending items. - -```ts type-equiv -/** One independently addressable accepted occurrence in an agent inbox. */ -interface InboxItem { - /** Agent-loop-minted occurrence identity. */ - readonly id: InboxItemId - /** Identified message delivered by the caller. */ - readonly message: UserMessage - /** Acceptance-time FIFO classification. */ - readonly placement: InboxPlacement -} -``` - -```ts type-equiv -/** A user-requested mutation of one still-pending queued occurrence. */ -type InboxAction = - | { readonly kind: 'edit'; readonly content: ContentBlock[] } - | { readonly kind: 'remove' } - | { readonly kind: 'steer' } -``` - -```ts type-equiv -/** Result of applying an inbox action at the synchronous ownership boundary. */ -type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' -``` - -```ts type-equiv -/** - * Options for the unified {@link Agent.send} primitive over the - * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} - * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and - * {@link Agent.inject} (`next-step`/no-wakeup). - * - * The object is complete so routing policy is explicit. - */ -interface SendOptions { - /** Queue the item joins. */ - target: SendTarget - /** - * Whether this item makes the model run: wake a parked driver (`next-turn`) - * or force a continuation step (`next-step` while running). A `false` - * `next-turn` item queues without waking; a `false` - * `next-step` item attaches durable context without forcing another step - * (the injection preset). - */ - wakeup: boolean -} -``` - -The fixed-preset aliases own `target` and `wakeup`; their already identified `UserMessage` carries role, content, and provenance. Its `MessageId` remains stable when an edit replaces content or strict steer transfers the immutable message. The original queued occurrence ends and strict steer accepts a new steering occurrence with a distinct `InboxItemId`. Injection bypasses the FIFOs and never appears on inbox lifecycle events. - -```ts type-equiv -/** Options for {@link Agent.cancel}. */ -interface CancelOptions { - /** - * Preserve queued and steering inbox items instead of discarding them. The - * active turn is still aborted, but un-started and pending work survives for a - * later turn and no `agent/inbox/discard` fires. - */ - keepInbox?: boolean -} -``` - -`SteeringReceipt.outcome` always resolves. `admitted` identifies the turn and step whose immutable request history contains that exact message; `rejected` means lifecycle or terminal policy discarded it first. Synchronous input validation still throws from `steer()`. - -```ts type-equiv -/** Stable runtime cause accepted by {@link Agent.cancel}. */ -type AgentCancelCause = - | { readonly kind: 'user' } - | { readonly kind: 'parent' } -``` - -`Agent` is an interface over the public live-agent contract. Concrete drivers own the `followup`/`steer`/`inject` aliases and route them through `send`'s (`target` × `wakeup`) matrix. - -```ts type-equiv -/** - * Public live-agent handle with aliases over the unified delivery primitive. - * @typert object - */ +/** Public live-agent handle. */ interface Agent { /** The single identity shared with {@link session}. */ readonly id: SessionId @@ -228,78 +65,28 @@ interface Agent { readonly options: AgentOptions /** The live session this agent drives; its log is the durable source of truth. */ readonly session: Session + /** The agent-owned projection of durable pending work. */ + readonly inbox: Inbox /** The current lifecycle state, mirrored on every `agent/status` transition. */ readonly status: AgentStatus - /** - * Whether a `next-step` send currently stages for prompt admission or the - * open turn. Unlike {@link status}, this excludes admission exit and turn - * settlement, when a waking `next-step` send becomes a queued follow-up. - */ - readonly acceptsNextStep: boolean /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ readonly ctx: Context - /** - * The unified delivery primitive over the (`target` × `wakeup`) matrix. - * It routes the caller's typed content and source as follows: - * - * - `next-turn` queues an item that becomes the sole ordinary message of its - * own FIFO-ordered turn; `wakeup:true` wakes a - * parked driver, while `wakeup:false` queues without waking. - * - `next-step` with `wakeup:true` stages steering during prompt admission - * or an open turn; outside that window it falls back to a woken - * `next-turn`. - * - `next-step` with `wakeup:false` injects durable model-facing context - * without running the model: admission or an open turn stages it for the - * next safe log position, while an injection outside that window appends - * immediately without opening a turn. If admission closes without a turn, - * a context-only boundary appends immediately; context staged beside - * steering remains pending with it. - * The agent publishes or queues the identified frozen message as-is. - * @param message - identified model-facing content and its producer provenance. - * @param options - target queue and wakeup decision. - */ - send(message: UserMessage, options: SendOptions): void - - /** - * Reserve admission of the next ordinary turn while this agent is idle, so an - * operation can mutate durable history before any queued prompt derives a - * request from it. Already-accepted waking work has right of way, including a - * send whose wake is still a pending microtask. Later sends keep their - * ordinary placement, FIFO order, and `wakeup` facts, and - * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes - * a queued follow-up rather than steering; cancellation and disposal may - * still discard them. {@link inject} is not withheld. {@link whenIdle} treats - * a live reservation as activity, while lifecycle teardown does not await it. - * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. - */ - reserveTurnAdmission(): (() => void) | undefined - - /** - * Mutate one still-pending queued occurrence synchronously. Editing preserves - * the message identity and queue position; removal publishes its terminal - * discard. Steer strictly transfers the message into the current next-step - * window, or returns `steer-unavailable` without changing the queued - * occurrence. Steering occurrences and driver-claimed items return - * `not-found`. - * @param id - independently addressable queued occurrence. - * @param action - edit, remove, or strict steer operation. - * @returns the applied outcome or the reason no mutation occurred. - */ - updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult - /** * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn. An effective call first emits `agent/cancel-requested` with the - * resolved typed cause. The first cause wins for the active turn, and - * `whenIdle()` resolves after cancellation reaches quiescence. Idle - * cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the current turn signal. + * turn or between-turn task. The first cause wins for that activity. With no + * active activity, cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the active operation signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ cancel(cause: AgentCancelCause, options?: CancelOptions): void - /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ + /** + * Resolve after the current whole-agent activity reaches quiescence. This + * follows replacement work started before the observed driver retires, + * but does not identify the settlement of any particular message. + * @returns fulfillment after no active driver or maintenance task remains. + */ whenIdle(): Promise /** @@ -334,35 +121,86 @@ interface Agent { followup(message: UserMessage): void /** - * Submit steering with a message-owned admission receipt — the - * `next-step`/wakeup preset of {@link send}. During prompt admission or an - * open turn, the message waits in the steering FIFO until a committed step - * snapshots it; outside that window it enters the ordinary queued FIFO. The - * receipt resolves `admitted` only after the message joins that step's - * immutable request history, or `rejected` when terminal policy, - * cancellation, or disposal discards it first. A non-terminal turn close may - * leave it staged for a later admitted prompt without settling the receipt. + * Submit steering for the nearest step. An idle driver starts a turn; + * a running driver consumes it at its next step boundary. + * A rejected step leaves steering parked in the inbox until the next + * wake; cancellation or disposal may discard pending steering. * @param message - identified steering content and its producer provenance. - * @returns the receipt for this exact message's eventual admission outcome. */ - steer(message: UserMessage): SteeringReceipt + steer(message: UserMessage): void /** - * Append model-facing context without running the model — the - * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn - * stages it at the next safe log position; outside that window it appends - * immediately without opening a turn. If admission closes without a turn, - * a context-only boundary appends immediately; context staged beside - * steering remains pending with it. + * Queue model-facing context for the next pre-step without waking the + * driver. A running driver claims it at the nearest later step boundary; + * idle drivers leave it pending until follow-up or steering + * wakes them. It may miss a request whose pre-step already claimed its + * batch. Cancellation or disposal may discard pending context. * @param message - identified injected context and its producer provenance. */ inject(message: UserMessage): void } ``` -`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `acceptsNextStep` is the narrower routing predicate for callers that must choose between steering the current admission/turn and submitting a fresh admitted prompt. A live turn-admission reservation is quiescence-relevant without changing `status` or turning later queue entries into steering; its only authority is to defer the next driver claim until release. `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default. +```ts type-equiv +/** + * An agent's lifecycle state, emitted on every transition as `agent/status`: + * `idle` means no driver is active; `running` begins when waking input starts + * cancellable pre-step processing and lasts while the driver drains, + * closes, or checkpoints turns. Disposal removes the agent from its registry; + * it is not a third observable status. + */ +type AgentStatus = 'idle' | 'running' +``` -The cause is a TypeScript-enforced same-process input. An active `TurnCancellation` holder copies its discriminant into the runtime-only `AbortSignal.reason` and is retired before `turn/end` publication; the frozen `AbortSignal.reason` remains readable after that retirement. Only the loop reads the cause (`user`, `parent`, or lifecycle-only `disposed`) back off its own machine-private signal at settlement — there is no public reader, and a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. +`running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `followup()` returns no handle: its `MessageId` identifies durable inbox insertion, claim, and discard facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)). + +```ts type-equiv +/** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */ +interface AgentOptions { + /** Provider route (must have a registered adapter at call time). */ + provider?: string + /** Model id interpreted by the selected provider adapter. */ + model?: string + /** Maximum output tokens for each conversation-model request. */ + maxTokens?: number +} +``` + +Dispatch requires `provider` and `model` after `agent/request`. When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. An agent-scoped `deployment:persona` prompt section may shadow the global default persona. + +The inbox is the delivery vocabulary — two ordered pending-message lists the agent owns as a durable projection: + +```ts type-equiv +/** One of the two ordered pending-message lists owned by an agent. */ +type InboxTarget = 'next-turn' | 'next-step' +``` + +Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.append`, `prepend`, `replace`, `remove`, `clear`, `splice`, and `claim` record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. `replace(messageId, newMessage)` and `remove(messageId)` locate the pending message across both lists; replacement may change identity and emits the old message as discarded followed by the new message as inserted. Ordinary removals and `clear()` are cancellations. `claim(target)` removes the proposed step batch — all `next-step` input plus, at a turn boundary, one `next-turn` message — through pure deletion splices without emitting discarded notifications, and the loop separately emits per-message claimed notifications. Whole-queue consumers such as UI projections reconstruct `nextTurn` and `nextStep` from the durable splices, while consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications. + +Cancellation: + +```ts type-equiv +/** Options for {@link Agent.cancel}. */ +interface CancelOptions { + /** + * Preserve queued and steering inbox items instead of discarding them. The + * active turn is still aborted, but un-started and pending work survives for a + * later turn and no canceled inbox splice is logged. + */ + keepInbox?: boolean | undefined +} +``` + +```ts type-equiv +/** Why an active agent driver was cancelled. */ +type AgentCancelCause = + | { readonly kind: 'user' } + | { readonly kind: 'parent' } + | { readonly kind: 'hook'; readonly reason: string } + | { readonly kind: 'disposed' } +``` + +The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result. The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits. @@ -372,22 +210,19 @@ The process-local initiator carried by `ctx.agents` is the exact `Agent` above, ## Interception decisions -Prompt and post-tool decisions use the same identified `UserMessage` shape as durable user-role input. Each `additionalContexts` entry becomes a separate `user/message`, preserving its identity and provenance. Hook bridges map their native decision fields onto these typed results. +Pre-step decisions use the same identified `UserMessage` shape as durable user-role input. The entered batch is authoritative and preserves every message's identity and provenance. Hook bridges map their native decision fields onto this typed result. Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) -`agent/prompt-submit` returns a `PromptDecision` before a turn opens. Allow may rewrite the claimed prompt or attach `additionalContexts`; block rejects admission without creating turn events: +`agent/pre-step` receives one payload carrying the exclusive claimed batch (`messages`), the proposed step's coordinates (`turn`, `step`), and the current turn's cancellation `signal`. The initial proposal runs inside an open turn before any step; a tool continuation may submit an empty claimed batch between steps: + +It returns a `PreStepDecision`. Reject opens no step. Enter supplies the complete message batch appended after `step/start`; claimed messages omitted by the final decision remain removed, while input inserted after the claim stays pending: ```ts type-equiv -/** - * Prompt interception result. `allow.content` replaces the prompt, while - * `additionalContexts` appends model-facing context before the turn starts. - * An `allow` returned by a listener is authoritative: a listener wrapping - * `next()` preserves both fields unless it intentionally replaces them. - */ -type PromptDecision = - | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } - | { kind: 'block'; reason: string } +/** Whether and with which messages the loop enters a proposed step. */ +type PreStepDecision = + | { kind: 'reject' } + | { kind: 'enter'; messages: UserMessage[] } ``` `agent/request-error` runs after a failed model step closes and before its turn closes. Listeners can repair durable state or await policy work while the failed turn's signal is still live. A handling listener returns `{ kind: 'retry' }` without calling `next()`; the default `undefined` leaves the failure terminal. @@ -397,12 +232,7 @@ type PromptDecision = type RequestErrorAction = { kind: 'retry' } | undefined ``` -```ts type-equiv -/** Model-request failure with an optional machine-routable provider code. */ -type RequestError = Error & { code?: string } -``` - -`agent/step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. +`agent/pre-step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain. `agent/session-start` carries a `SessionStartSource` (why the session lifecycle began; a bridge keys its SessionStart matcher on it): @@ -411,12 +241,71 @@ type RequestError = Error & { code?: string } type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact' ``` +## Sessions + +A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. Every entry carries a monotonic `seq`, a `time`, and a `type`-discriminated `data` payload; surface variants additionally carry `sourceEventSeqs` provenance and a `surfaceOp`. + +The `SessionEvent` envelope's exact conditional shape, the twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**. + ## `ToolDefinition` The one pipeline-authoring type that is core: what every registered tool *is* — a model-facing `ToolSchema` plus an `execute` function and optional final-content and UI callbacks. A tool author rarely constructs it by hand (the `defineTool` DSL builds it with typed args), but it is the contract the registry holds and the loop dispatches through. Its full fields, the `defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` typed schema DSL, the `ToolExecution`/`ToolExecutionResult` waterfall shapes, and the tool-presentation UI vocabulary are on **[tools.md](tools.md)**. +## Repo-wide type patterns + +Two patterns recur across every subsystem and are documented once, here. + +### The `…Map → derived-union` pattern + +Almost every extensible sum type in the harness follows one shape: an interface keyed by a discriminant tag (the `…Map`), from which the union is derived with `keyof`. Plugins add variants by **declaration merging** — no edit to the owning package. + +```ts ignore-check +// The pattern, schematically: +interface ThingMap { + 'a': { kind: 'a'; /* … */ } + 'b': { kind: 'b'; /* … */ } +} +type ThingKind = keyof ThingMap // 'a' | 'b' +type Thing = ThingMap[keyof ThingMap] // the discriminated union + +// A plugin extends it without touching the source package: +declare module '@deepseek-ai/dsh-llm' { + interface ThingMap { + 'c': { kind: 'c'; /* … */ } + } +} +``` + +Six canonical maps use this pattern; a plugin author extends these: + +| Map | Package | Derives | Catalog | +|---|---|---|---| +| `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](llm-streaming.md#content-blocks-and-messages) | +| `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](llm-streaming.md#the-model-request-and-result) | +| `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](session.md) | +| `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](session.md) | +| `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](session.md) | + +Two large discriminated unions are the ones consumers `switch` over most: **`StreamChunk`** (the streaming protocol) and **`SessionEvent`** (the log entry). Per the repo convention, `switch` on the tag — don't chain `if`s — so each arm narrows and a typo'd tag fails to compile. + +### Branded IDs + +IDs that cross package boundaries are **branded** — structurally strings, but non-interchangeable at the type level (a `SessionId` cannot be passed where a `CallId` is expected). Construction goes through a per-type factory; comparison, logging, and JSON behave as ordinary strings. + +The `Branded` primitive lives in its own type-only package, [dsh-brand](../../packages/util/brand) (no runtime code, no harness-package dependency), so any package can brand the ids it owns without depending on an unrelated capability package. + +Source: [`packages/util/brand/src/index.ts`](../../packages/util/brand/src/index.ts) + +```ts type-equiv +/** A string carrying a compile-time-only brand `B`. */ +type Branded = string & { readonly [BRAND]: B } +``` + +The two core IDs are `CallId` (correlates a tool call with its result; dsh-llm) and `SessionId` (the shared live agent and durable session identity; dsh-session). Capability packages brand their own ids too, such as `TaskId` in [tasks.md](tasks.md). + @@ -462,7 +351,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -634,35 +523,12 @@ list(): Agent[] roots(): Agent[] ``` -Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/index.ts) +Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) ### `agent/*` events - - -#### `agent/cancel-requested` — emit - -Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. - -```ts cordis-catalog -/** - * Effective broad cancellation was requested, before queued/outbox work - * is cleared or the active turn is aborted. This observe-only notification - * cannot veto cancellation; listener failures are contained. - * @param agent - the agent whose current work is being cancelled. - * @param cause - the explicit typed cancellation cause. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void -``` - -Types: [Scoped](scope.md) - -Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) - #### `agent/created` — emit @@ -676,11 +542,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param agent - the newly registered agent with its live session and completed setup. + * @param payload.agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, agent: Agent): void +'agent/created'(this: Scoped, payload: { agent: Agent }): void ``` Types: [Scoped](scope.md) @@ -698,11 +564,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param agent - the exact agent removed from the registry. + * @param payload.agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, agent: Agent): void +'agent/disposed'(this: Scoped, payload: { agent: Agent }): void ``` Types: [Scoped](scope.md) @@ -713,138 +579,111 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here (plus the - * logger) even when the error has no in-turn position for a durable record. - * @param agent - the agent whose turn errored. - * @param turn - the turn in which the failure surfaced. - * @param step - the step at which the failure surfaced. - * @param error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here even when + * the error has no in-turn position for a durable record. + * @param payload.agent - the agent whose turn errored. + * @param payload.turn - the turn in which the failure surfaced. + * @param payload.step - the step at which the failure surfaced. + * @param payload.error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void +'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/dequeue` — emit +#### `agent/inbox/claimed` — emit -The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. +One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. ```ts cordis-catalog /** - * The driver claimed one item out of the inbox: a queued item at a turn - * boundary, or steering drained between steps. Fires after the item leaves - * its FIFO and before it becomes a durable message. - * @param agent - the agent whose inbox item was claimed. - * @param item - the exact claimed occurrence. + * One message left the inbox inside its open turn. If the proposed step + * is rejected, the claimed message ends here: it is neither discarded nor + * re-emitted as a user/message, and the turn closes without a step. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the claimed message. + * @param payload.turn - the owning turn. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void +'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discard` — emit +#### `agent/inbox/discarded` — emit -Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. +One message was discarded from the live inbox. ```ts cordis-catalog /** - * Pending inbox items were dropped without delivering them, so every - * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR - * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, - * emits this after `agent/cancel-requested` when applicable and before - * aborting the active work. Fires once per drop with every dropped item. - * @param agent - the agent whose inbox items were dropped. - * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. + * One message was discarded from the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the discarded message. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void +'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/enqueue` — emit +#### `agent/inbox/inserted` — emit -An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. +One message entered the live inbox. ```ts cordis-catalog /** - * An item entered the queued or steering inbox. `placement` is the - * acceptance-time routing result; listeners must not reconstruct it from - * later agent or session state. - * @param agent - the owning agent. - * @param item - accepted occurrence, message, and resolved placement. + * One message entered the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the inserted message. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void +'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/update` — emit +#### `agent/pre-step` — waterfall -A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. +Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. ```ts cordis-catalog /** - * A still-pending queued item changed content. The item id, placement, and - * position remain stable while the event carries the replacement message. - * @param agent - the owning agent. - * @param item - the complete post-update occurrence. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void -``` - -Types: [Scoped](scope.md) - -Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) - - - -#### `agent/prompt-submit` — waterfall - -Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. - -```ts cordis-catalog -/** - * Allow, rewrite, or block one claimed prompt before it becomes a user - * message or opens a turn. Call `next()` for the unchanged default. The - * signal controls only this admission attempt; listeners may cooperate with - * it but must not retain it for a later attempt or turn. - * @param agent - the agent whose turn claimed the message. - * @param message - the frozen claimed message, including identity and source. - * @param signal - the current turn's explicit abort signal. + * Reject a proposed step or replace the messages that enter it. Calling + * `next()` preserves the current messages. + * @param payload.agent - the agent proposing the step. + * @param payload.messages - messages removed from the inbox for this step. + * @param payload.turn - the turn that will own the step. + * @param payload.step - the step proposed by the loop. + * @param payload.signal - the current turn's cancellation signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise +'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -863,14 +702,14 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param agent - the agent making the model call. - * @param turn - the open turn number. - * @param step - the step whose request this is. - * @param signal - the current turn's explicit abort signal. + * @param payload.agent - the agent making the model call. + * @param payload.turn - the open turn number. + * @param payload.step - the step whose request this is. + * @param payload.signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise +'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) @@ -881,28 +720,25 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle a model-request failure after its failed step has closed but - * before the failed turn closes. A listener returns `{ kind: 'retry' }` - * without calling `next()` when it owns the error, or calls `next()` to - * delegate. The default `undefined` leaves the failure terminal. - * @param agent - the agent whose request failed. - * @param turn - the open turn number. - * @param step - the failed step number. - * @param error - the original model-request failure. - * @param failure - serializable facts normalized at the final adapter boundary. - * @param priorFailures - immutable failures that already authorized another - * retry turn in this consecutive sequence. - * @param retryPolicy - immutable policy of the adapter registration that served - * the failed request, or `undefined` if no final adapter served it. - * @param signal - the turn abort signal. + * Handle one failed model-request attempt before the loop retries or closes + * its step. A listener returns `{ kind: 'retry' }` without calling `next()` + * when it owns recovery, or calls `next()` to delegate. The default + * `undefined` leaves the failure terminal. + * @param payload.agent - the agent whose request failed. + * @param payload.turn - the turn containing the failed request. + * @param payload.step - the step containing the failed request attempt. + * @param payload.provider - the provider selected for the failed request. + * @param payload.failure - serializable facts normalized at the final adapter boundary. + * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. + * @param payload.signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise +'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -921,12 +757,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param agent - the agent whose session lifecycle began. - * @param source - why the session started (fresh startup, resume, …). + * @param payload.agent - the agent whose session lifecycle began. + * @param payload.source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void +'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void ``` Types: [Scoped](scope.md) @@ -937,18 +773,19 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. +Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). `send()` does not enter - * `running` synchronously; drive lifecycle from this event. - * @param agent - the agent whose status flipped. - * @param status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). A waking delivery enters + * `running` synchronously after reserving cancellation; `idle` means no + * driver remains scheduled or active. + * @param payload.agent - the agent whose status flipped. + * @param payload.status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void +'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void ``` Types: [Scoped](scope.md) @@ -959,7 +796,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. ```ts cordis-catalog /** @@ -969,14 +806,17 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. - * @param agent - the agent whose turn is at its stop boundary. - * @param turn - the turn about to close. - * @param signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. The conclusion + * never short-circuits already-submitted next-step work: same-step + * `additionalContexts` or racing steering still runs, and the turn + * closes only when that inbox drains. + * @param payload.agent - the agent whose turn is at its stop boundary. + * @param payload.turn - the turn about to close. + * @param payload.signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void +'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void ``` Types: [Scoped](scope.md) @@ -999,12 +839,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param sessionId - exact shared agent/session identity that failed startup. - * @param error - persistence, setup, or publication failure. + * @param payload.sessionId - exact shared agent/session identity that failed startup. + * @param payload.error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void +'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void ``` -Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index 276dc4f99f..6878300dc2 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -2,11 +2,11 @@ [English](core.md) | 中文 -**核心**子系统即 [`packages/core`](../../packages/core/README.md)——每个组合都会启动的控制主干:事件溯源的会话日志、系统提示词组装、工具注册表、agent 词汇,以及驱动它们的具体循环。本页拥有主干的共享词汇——`Agent` 句柄及其投递与拦截契约,以及全仓通用的类型模式——并指引到该组的专属页面;目录索引见[子系统 README](README.md)。 +**核心**子系统即 [`packages/core`](../../packages/core/README.md)——每个组合都会启动的控制主干:事件溯源的会话日志、系统提示词组装、工具注册表、agent 词汇,以及驱动它们的具体循环。本页拥有 `agent`/`agent-loop` 这对包所声明的内容——agent 如何被创建与拥有,以及 `Agent` 句柄及其投递、取消与拦截契约——外加每个子系统都遵循的两个类型模式;该组的专属页面与目录其余部分见[子系统 README](README.md)。 ## 主干逐包速览 -一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的 prompt,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。 +一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的 prompt,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md)。 | 包 | 负责内容 | 页面 | |---|---|---| @@ -19,13 +19,251 @@ `scope/` 是这里唯一的非服务包:一个零依赖库(`createScope`/`scopeOf`/`scopeTarget`),在模块图中位于 `session/` 与 `system-prompt/` 之下,正是为了让它们消费它而不形成环。`agent-loop` 是 `agent` seam 的唯一具体实现,放在这里因为它是 harness 的默认产品循环;它在 `ctx.agents.withInitiator()` 内运行每个 driver。扩展插件依赖 `agent`——包括需要发起 Agent 时——而绝不直接依赖 `agent-loop`,因此循环保持可替换。把这条主干接成可运行 agent 的默认组合是 [`examples/agent-spine-demo`](../../packages/examples/agent-spine-demo/README.md)。 - + -## 本页拥有什么 +## 创建与所有权 -循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md);会话事件、提示词组装与工具词汇在上表该组的专属页面。留在本页的是所有部分共享的词汇:`Agent` 句柄及其投递、取消与拦截契约(由 `packages/core/agent` 声明)、`SessionEvent` 信封,以及每个子系统都遵循的两个类型模式。范围界定规则记录在[子系统目录 Agent Note](../../.agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md):你编写、持有或接收的类型记录在其声明子系统所在之处;为它提供类型推导、渲染或持久化的机制留在该机制自己的页面。 +消费方通过 `ctx.agents` 创建 agent——`create()` 在一个调用方提供的 `SessionId` 下构建全新会话与 agent,`resume()` 先加载持久会话——或者通过循环的声明式配置条目创建。编程式创建返回归属所有者的句柄: -## `…Map → derived-union` 模式 +源码:[`packages/core/agent/src/index.ts`](../../packages/core/agent/src/index.ts) + +```ts type-equiv +/** + * An owned agent plus its disposer, returned by {@link AgentRegistry.create} / + * {@link AgentRegistry.resume}. The disposer is a CAPABILITY: among consumers, + * only the holder can tear this agent down. The registered factory provider is + * also a structural owner because the scoped agent depends on that provider's + * service surface; provider unload stops and drains every live handle it made. + * `dispose()` stops the loop, awaits its exit, unregisters the agent, removes + * its session from the store, and finally unwinds its scoped world. + * + * `ctx.agents.get(id)` still returns a bare {@link Agent} — the handle is + * exposed only to the consumer owner that created it; the structural provider + * reaches the same teardown internally. Config-created agents (the loop's own + * startup) are owned by the loop fiber and never need a handle. + */ +interface AgentHandle { + agent: Agent + dispose(): Promise +} +``` + +`CreateAgentOptions` 携带共享标识以及新 agent 发布前所需的一切:会话元数据(`meta`——已校验的 `cwd`、fork 谱系、seed 边界、来源分类、委派深度)、fork 用的可选 `seed` 回放前缀、按 agent 的 `AgentOptions`、仅创建期有效的取消 `signal`,以及 `setup`。`ResumeAgentOptions` 是持久标识的对应物:`resumeSessionId`、`agentOptions`、`signal` 与 `setup`。`setup` 回调(`AgentSetup`)在两个 id 都尚未发布时组装 agent 的作用域世界——凡经 `agentCtx` 注册的内容都先于 `agent/created` 与第一次提示词组装存在——并可返回一个在发布前一刻调用的同步 commit;setup 拒绝、commit 抛出或所有者 dispose 都会回滚事务,两个 id 均不发布。 + +`AgentFactory` 是注册表背后的创建 seam:循环经 `ctx.agents.setFactory()` 注册其工厂,因此消费方面向 `ctx.agents` 编程,无需依赖具体循环包。确切的 `create`/`resume` 签名及其回滚契约见下方[生成区块](#ctxagents--agentregistry)。 + + + +## Agent 句柄 + +`Agent` 是每个插件(UI、钩子、orchestrator)面向编程的 surface;`ctx.agents.get(id)` 返回它,[发起者作用域](#initiating-agent)携带它。具体实现为 dsh-agent-loop 包内部细节;循环外没有任何组件依赖它。统一的 `send` 方法直接暴露 target 与 wakeup 路由;`followup`、`steer` 与 `inject` 是固定预设的别名方法。 + +源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) + +```ts type-equiv +/** Public live-agent handle. */ +interface Agent { + /** The single identity shared with {@link session}. */ + readonly id: SessionId + /** The provider route and model this agent's requests use. */ + readonly options: AgentOptions + /** The live session this agent drives; its log is the durable source of truth. */ + readonly session: Session + /** The agent-owned projection of durable pending work. */ + readonly inbox: Inbox + /** The current lifecycle state, mirrored on every `agent/status` transition. */ + readonly status: AgentStatus + /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ + readonly ctx: Context + + /** + * Clear queued and steering work — unless `keepInbox` — and abort the active + * turn or between-turn task. The first cause wins for that activity. With no + * active activity, cancellation is a no-op and does not arm later work. + * @param cause - the stable caller intent carried by the active operation signal. + * @param options - cancellation options; `keepInbox` preserves pending work. + */ + cancel(cause: AgentCancelCause, options?: CancelOptions): void + + /** + * Resolve after the current whole-agent activity reaches quiescence. This + * follows replacement work started before the observed driver retires, + * but does not identify the settlement of any particular message. + * @returns fulfillment after no active driver or maintenance task remains. + */ + whenIdle(): Promise + + /** + * Run one non-turn maintenance task from the true idle phase. The task starts + * synchronously after claiming that phase; later waking input remains in the + * inbox until the task settles, while public status stays `idle`. + * `whenIdle()` follows both the task and any waking work released behind it. + * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}. + * @throws synchronously when turn-driving or another maintenance task already owns the agent. + * @returns the task promise. + */ + runMaintenance(task: (signal: AbortSignal) => Promise): Promise + + /** + * Route identified input to an inbox boundary and optionally wake the driver. + * Waking input submitted after active cancellation is queued for the next + * turn and runs when the aborted activity converges to idle; a `disposed` + * cancel leaves it parked. A wake submitted while already idle always opens + * its turn boundary, even when its message is cleared before the driver + * claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)). + * @param message - identified content and its producer provenance. + * @param target - the preferred next-turn or next-step inbox boundary. + * @param wakeup - whether delivery may wake the driver. + */ + send(message: UserMessage, target: InboxTarget, wakeup: boolean): void + + /** + * Queue an ordinary follow-up turn and wake the driver. The item becomes the + * sole ordinary message of its own turn. + * @param message - identified prompt content and its producer provenance. + */ + followup(message: UserMessage): void + + /** + * Submit steering for the nearest step. An idle driver starts a turn; + * a running driver consumes it at its next step boundary. + * A rejected step leaves steering parked in the inbox until the next + * wake; cancellation or disposal may discard pending steering. + * @param message - identified steering content and its producer provenance. + */ + steer(message: UserMessage): void + + /** + * Queue model-facing context for the next pre-step without waking the + * driver. A running driver claims it at the nearest later step boundary; + * idle drivers leave it pending until follow-up or steering + * wakes them. It may miss a request whose pre-step already claimed its + * batch. Cancellation or disposal may discard pending context. + * @param message - identified injected context and its producer provenance. + */ + inject(message: UserMessage): void +} +``` + +```ts type-equiv +/** + * An agent's lifecycle state, emitted on every transition as `agent/status`: + * `idle` means no driver is active; `running` begins when waking input starts + * cancellable pre-step processing and lasts while the driver drains, + * closes, or checkpoints turns. Disposal removes the agent from its registry; + * it is not a third observable status. + */ +type AgentStatus = 'idle' | 'running' +``` + +`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`followup()` 不返回句柄:其 `MessageId` 标识的是持久的 inbox 插入、认领与丢弃事实,而非之后的助手输出或轮次结束。`whenIdle()` 观察的是整个 agent,因此只有当调用方明确拥有从回执到空闲的这段区间时,才能把它称为一次 run([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。 + +```ts type-equiv +/** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */ +interface AgentOptions { + /** Provider route (must have a registered adapter at call time). */ + provider?: string + /** Model id interpreted by the selected provider adapter. */ + model?: string + /** Maximum output tokens for each conversation-model request. */ + maxTokens?: number +} +``` + +在 `agent/request` 之后,分发要求 `provider` 与 `model` 都存在。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。agent 作用域的 `deployment:persona` 提示词段落可以遮蔽全局默认 persona。 + +inbox 即投递词汇——agent 以持久投影形式拥有的两条有序待处理消息列表: + +```ts type-equiv +/** One of the two ordered pending-message lists owned by an agent. */ +type InboxTarget = 'next-turn' | 'next-step' +``` + +每个待处理入队项就是其 `UserMessage`;`MessageId` 是唯一标识。`Inbox.append`、`prepend`、`replace`、`remove`、`clear`、`splice` 与 `claim` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。`replace(messageId, newMessage)` 与 `remove(messageId)` 通过 `MessageId` 跨两份列表定位待处理消息;替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。普通删除和 `clear()` 都表示取消。`claim(target)` 通过无 outcome 的纯删除 splice 移除拟进入步骤的批次——全部 `next-step` 输入,外加轮次边界上的一条 `next-turn` 消息——且不发出 discarded 通知;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` 与 `nextStep`,而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知。 + +取消: + +```ts type-equiv +/** Options for {@link Agent.cancel}. */ +interface CancelOptions { + /** + * Preserve queued and steering inbox items instead of discarding them. The + * active turn is still aborted, but un-started and pending work survives for a + * later turn and no canceled inbox splice is logged. + */ + keepInbox?: boolean | undefined +} +``` + +```ts type-equiv +/** Why an active agent driver was cancelled. */ +type AgentCancelCause = + | { readonly kind: 'user' } + | { readonly kind: 'parent' } + | { readonly kind: 'hook'; readonly reason: string } + | { readonly kind: 'disposed' } +``` + +cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`;signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 + +[事件分类](../architecture.md#event)拥有 `agent/*` 生命周期、检查点与 waterfall(瀑布式事件)契约。轮次和步骤边界是持久会话事件,而不是 agent emit。 + + + +## 发起 Agent + +`ctx.agents` 携带的进程本地 initiator 就是上面的确切 `Agent`,不是单独的 frame 或复制的标识。环境中存在该值既不能证明存活,也不代表授权;其生命周期与边界规则由 [initiator 作用域决策](../../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)规定。 + +## 拦截决策 + +pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。 + +源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) + +`agent/pre-step` 接收一个 payload,携带独占的已领取批次(`messages`)、拟进入步骤的坐标(`turn`、`step`)与当前轮次的取消 `signal`。首次提案在已打开的轮次内、任何步骤开始前运行;工具 continuation 可以在步骤之间提交空的已领取批次: + +它返回 `PreStepDecision`。reject 不会打开步骤。enter 提供在 `step/start` 后追加的完整消息批次;最终决策省略的已领取消息保持已删除,而领取后插入的输入仍留待后续处理: + +```ts type-equiv +/** Whether and with which messages the loop enters a proposed step. */ +type PreStepDecision = + | { kind: 'reject' } + | { kind: 'enter'; messages: UserMessage[] } +``` + +`agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。 + +```ts type-equiv +/** Action returned by a listener that owns model-request recovery. */ +type RequestErrorAction = { kind: 'retry' } | undefined +``` + +`agent/pre-step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 + +`agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart): + +```ts type-equiv +/** Why a session lifecycle began; seeded creates are `startup`, while persisted loads are `resume`. */ +type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact' +``` + +## 会话 + +`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM(大语言模型)消息历史从日志*派生*(`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payload;surface 变体还额外携带 `sourceEventSeqs` provenance 与 `surfaceOp`。 + +`SessionEvent` 信封的确切条件形状、十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 + +## `ToolDefinition` + +唯一属于核心的流水线编写类型:每个已注册工具*是什么*——一个面向模型的 `ToolSchema` 加上一个 `execute` 函数,以及可选的最终内容回调与 UI 回调。工具作者很少手动构造它(`defineTool` DSL 会用类型化参数构建),但它是注册表持有、循环分发所经过的契约。 + +其完整字段、`defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` 类型化 schema DSL、`ToolExecution`/`ToolExecutionResult` waterfall 形状,以及工具展示 UI 词汇在 **[tools.md](tools.md)** 中。 + +## 全仓通用类型模式 + +两个模式在每个子系统中反复出现,只在此处记录一次。 + +### `…Map → derived-union` 模式 harness 中几乎所有可扩展的和类型都遵循同一形状:一个以判别标签为键的接口(`…Map`),联合类型由 `keyof` 派生。插件通过**声明合并**添加变体——无需修改拥有该类型的包(package)。 @@ -61,7 +299,7 @@ declare module '@deepseek-ai/dsh-llm' { -## 品牌化 ID +### 品牌化 ID 跨越包边界的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。 @@ -76,351 +314,6 @@ type Branded = string & { readonly [BRAND]: B } 两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [tasks.md](tasks.md) 中的 `TaskId`。 -## 会话 - -`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM(大语言模型)消息历史从日志*派生*(`deriveMessages()`),而非单独存储。事件词汇从 `SessionEventMap` 派生: - -源码:[`packages/core/session/src/types.ts`](../../packages/core/session/src/types.ts) - -```ts type-equiv -/** - * One immutable entry in the session log. - * - * A proper discriminated union over `type` (not independent `type`/`data` - * unions), so `switch (event.type)` narrows `event.data` without casts. - * - * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional: - * they only exist on {@link SurfaceEventType} variants (`user/message`, - * `assistant/message`, `tool/result`, `steering/message`). - * Non-surface events (boundary markers, chunks, usage, errors) never carry - * surface metadata — the compiler enforces this at `Session.append()` - * call sites. - */ -type SessionEvent = { - [K in SessionEventType]: { - type: K - /** Monotonic sequence number within the session. */ - seq: number - /** Unix epoch milliseconds. */ - time: number - data: SessionEventMap[K] - } & (K extends SurfaceEventType ? { - /** - * Seq numbers of events that are provenance sources of this event - * (e.g. the `assistant/chunk` seqs that built an `assistant/message`, - * or the surface nodes shadowed by a compaction replace node). An - * `assistant/message` may carry a present empty array for a known empty - * provider stream; omission means unrecorded provenance. - */ - sourceEventSeqs?: number[] - /** How this event entered the surface; absent for non-surface events. */ - surfaceOp?: SurfaceOp - } : object) -}[T] -``` - -十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。 - - - -## Agent 句柄 - -`Agent` 是每个插件(UI、钩子、orchestrator)面向编程的 surface。具体实现为 dsh-agent-loop 包内部细节;循环外没有任何组件依赖它。 - -源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) - -```ts type-equiv -/** - * Which inbox queue a {@link Agent.send} item joins: - * - `next-turn` — the item becomes its own turn, claimed at a turn boundary. - * - `next-step` — during prompt admission or an open turn, the item stages for - * the next safe step boundary; otherwise it is promoted per its `wakeup` - * flag. - */ -type SendTarget = 'next-turn' | 'next-step' -``` - -```ts type-equiv -/** Resolved inbox placement reported when an accepted message is enqueued. */ -type InboxPlacement = 'queued' | 'steering' -``` - -`InboxItemId` 是为每次获准进入 FIFO 的项铸造的进程本地品牌字符串。它有意区别于 `MessageId`:同一条不可变消息发送两次,会创建两个可独立寻址的待处理项。 - -```ts type-equiv -/** One independently addressable accepted occurrence in an agent inbox. */ -interface InboxItem { - /** Agent-loop-minted occurrence identity. */ - readonly id: InboxItemId - /** Identified message delivered by the caller. */ - readonly message: UserMessage - /** Acceptance-time FIFO classification. */ - readonly placement: InboxPlacement -} -``` - -```ts type-equiv -/** A user-requested mutation of one still-pending queued occurrence. */ -type InboxAction = - | { readonly kind: 'edit'; readonly content: ContentBlock[] } - | { readonly kind: 'remove' } - | { readonly kind: 'steer' } -``` - -```ts type-equiv -/** Result of applying an inbox action at the synchronous ownership boundary. */ -type InboxActionResult = 'applied' | 'not-found' | 'steer-unavailable' -``` - -```ts type-equiv -/** - * Options for the unified {@link Agent.send} primitive over the - * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup} - * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and - * {@link Agent.inject} (`next-step`/no-wakeup). - * - * The object is complete so routing policy is explicit. - */ -interface SendOptions { - /** Queue the item joins. */ - target: SendTarget - /** - * Whether this item makes the model run: wake a parked driver (`next-turn`) - * or force a continuation step (`next-step` while running). A `false` - * `next-turn` item queues without waking; a `false` - * `next-step` item attaches durable context without forcing another step - * (the injection preset). - */ - wakeup: boolean -} -``` - -固定预设的别名方法自带 `target` 与 `wakeup`;其已有标识的 `UserMessage` 会携带角色、内容与 provenance。编辑替换内容或严格 steering(中途引导)转移不可变消息时,其 `MessageId` 都保持稳定。原 queued 单次入队项会结束,严格 steering 则接受一个具有不同 `InboxItemId` 的新 steering 单次入队项。注入绕过两个 FIFO,从不出现在 inbox 生命周期事件中。 - -```ts type-equiv -/** Options for {@link Agent.cancel}. */ -interface CancelOptions { - /** - * Preserve queued and steering inbox items instead of discarding them. The - * active turn is still aborted, but un-started and pending work survives for a - * later turn and no `agent/inbox/discard` fires. - */ - keepInbox?: boolean -} -``` - -`SteeringReceipt.outcome` 始终会解析。`admitted` 标识其不可变请求历史包含该确切消息的轮次与步骤;`rejected` 表示生命周期或终止策略先丢弃了该消息。同步输入校验仍会从 `steer()` 抛出异常。 - -```ts type-equiv -/** Stable runtime cause accepted by {@link Agent.cancel}. */ -type AgentCancelCause = - | { readonly kind: 'user' } - | { readonly kind: 'parent' } -``` - -`Agent` 是覆盖公开活跃 agent 契约的接口。具体驱动器拥有 `followup`/`steer`/`inject` 别名方法,并将它们经由 `send` 的(`target` × `wakeup`)矩阵路由。 - -```ts type-equiv -/** - * Public live-agent handle with aliases over the unified delivery primitive. - * @typert object - */ -interface Agent { - /** The single identity shared with {@link session}. */ - readonly id: SessionId - /** The provider route and model this agent's requests use. */ - readonly options: AgentOptions - /** The live session this agent drives; its log is the durable source of truth. */ - readonly session: Session - /** The current lifecycle state, mirrored on every `agent/status` transition. */ - readonly status: AgentStatus - /** - * Whether a `next-step` send currently stages for prompt admission or the - * open turn. Unlike {@link status}, this excludes admission exit and turn - * settlement, when a waking `next-step` send becomes a queued follow-up. - */ - readonly acceptsNextStep: boolean - /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */ - readonly ctx: Context - - /** - * The unified delivery primitive over the (`target` × `wakeup`) matrix. - * It routes the caller's typed content and source as follows: - * - * - `next-turn` queues an item that becomes the sole ordinary message of its - * own FIFO-ordered turn; `wakeup:true` wakes a - * parked driver, while `wakeup:false` queues without waking. - * - `next-step` with `wakeup:true` stages steering during prompt admission - * or an open turn; outside that window it falls back to a woken - * `next-turn`. - * - `next-step` with `wakeup:false` injects durable model-facing context - * without running the model: admission or an open turn stages it for the - * next safe log position, while an injection outside that window appends - * immediately without opening a turn. If admission closes without a turn, - * a context-only boundary appends immediately; context staged beside - * steering remains pending with it. - * The agent publishes or queues the identified frozen message as-is. - * @param message - identified model-facing content and its producer provenance. - * @param options - target queue and wakeup decision. - */ - send(message: UserMessage, options: SendOptions): void - - /** - * Reserve admission of the next ordinary turn while this agent is idle, so an - * operation can mutate durable history before any queued prompt derives a - * request from it. Already-accepted waking work has right of way, including a - * send whose wake is still a pending microtask. Later sends keep their - * ordinary placement, FIFO order, and `wakeup` facts, and - * {@link acceptsNextStep} stays `false`, so a waking `next-step` send becomes - * a queued follow-up rather than steering; cancellation and disposal may - * still discard them. {@link inject} is not withheld. {@link whenIdle} treats - * a live reservation as activity, while lifecycle teardown does not await it. - * @returns the idempotent release, or `undefined` when the agent is running, already reserved, or already committed to waking work. - */ - reserveTurnAdmission(): (() => void) | undefined - - /** - * Mutate one still-pending queued occurrence synchronously. Editing preserves - * the message identity and queue position; removal publishes its terminal - * discard. Steer strictly transfers the message into the current next-step - * window, or returns `steer-unavailable` without changing the queued - * occurrence. Steering occurrences and driver-claimed items return - * `not-found`. - * @param id - independently addressable queued occurrence. - * @param action - edit, remove, or strict steer operation. - * @returns the applied outcome or the reason no mutation occurred. - */ - updateInbox(id: InboxItemId, action: InboxAction): InboxActionResult - - /** - * Clear queued and steering work — unless `keepInbox` — and abort the active - * turn. An effective call first emits `agent/cancel-requested` with the - * resolved typed cause. The first cause wins for the active turn, and - * `whenIdle()` resolves after cancellation reaches quiescence. Idle - * cancellation is a no-op and does not arm later work. - * @param cause - the stable caller intent carried by the current turn signal. - * @param options - cancellation options; `keepInbox` preserves pending work. - */ - cancel(cause: AgentCancelCause, options?: CancelOptions): void - - /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */ - whenIdle(): Promise - - /** - * Run one non-turn maintenance task from the true idle phase. The task starts - * synchronously after claiming that phase; later waking input remains in the - * inbox until the task settles, while public status stays `idle`. - * `whenIdle()` follows both the task and any waking work released behind it. - * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}. - * @throws synchronously when turn-driving or another maintenance task already owns the agent. - * @returns the task promise. - */ - runMaintenance(task: (signal: AbortSignal) => Promise): Promise - - /** - * Route identified input to an inbox boundary and optionally wake the driver. - * Waking input submitted after active cancellation is queued for the next - * turn and runs when the aborted activity converges to idle; a `disposed` - * cancel leaves it parked. A wake submitted while already idle always opens - * its turn boundary, even when its message is cleared before the driver - * claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)). - * @param message - identified content and its producer provenance. - * @param target - the preferred next-turn or next-step inbox boundary. - * @param wakeup - whether delivery may wake the driver. - */ - send(message: UserMessage, target: InboxTarget, wakeup: boolean): void - - /** - * Queue an ordinary follow-up turn and wake the driver. The item becomes the - * sole ordinary message of its own turn. - * @param message - identified prompt content and its producer provenance. - */ - followup(message: UserMessage): void - - /** - * Submit steering with a message-owned admission receipt — the - * `next-step`/wakeup preset of {@link send}. During prompt admission or an - * open turn, the message waits in the steering FIFO until a committed step - * snapshots it; outside that window it enters the ordinary queued FIFO. The - * receipt resolves `admitted` only after the message joins that step's - * immutable request history, or `rejected` when terminal policy, - * cancellation, or disposal discards it first. A non-terminal turn close may - * leave it staged for a later admitted prompt without settling the receipt. - * @param message - identified steering content and its producer provenance. - * @returns the receipt for this exact message's eventual admission outcome. - */ - steer(message: UserMessage): SteeringReceipt - - /** - * Append model-facing context without running the model — the - * `next-step`/no-wakeup preset of {@link send}. Admission or an open turn - * stages it at the next safe log position; outside that window it appends - * immediately without opening a turn. If admission closes without a turn, - * a context-only boundary appends immediately; context staged beside - * steering remains pending with it. - * @param message - identified injected context and its producer provenance. - */ - inject(message: UserMessage): void -} -``` - -`AgentStatus` 为 `'idle' | 'running'`,`SessionId` 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。对于需要在把输入作为 steering 加入当前提示词准入/轮次,还是提交为一个新的待准入提示词之间做选择的调用方,`acceptsNextStep` 才是更窄且准确的路由判断条件。活动的轮次接纳预留与完全停稳相关,但不会改变 `status`,也不会把之后的队列项变成 steering;它的唯一权限是将驱动器的下一次认领延迟到释放时。`AgentOptions` 可合并扩展:core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有:agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。 - -cause 是由 TypeScript 强制约束的同进程输入。活跃的 `TurnCancellation` 持有者会把其判别字段复制到仅运行时的 `AbortSignal.reason`,并在发布 `turn/end` 前退役;冻结后的 `AbortSignal.reason` 仍可读取。只有 loop 会在结算时从自己机器私有的 signal 上读回 cause(`user`、`parent` 或仅用于生命周期的 `disposed`)——不存在公开的读取器,signal 也不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。 - -[事件分类](../architecture.md#event)拥有 `agent/*` 生命周期、检查点与 waterfall(瀑布式事件)契约。轮次和步骤边界是持久会话事件,而不是 agent emit。 - -## 发起 Agent - -`ctx.agents` 携带的进程本地 initiator 就是上面的确切 `Agent`,不是单独的 frame 或复制的标识。环境中存在该值既不能证明存活,也不代表授权;其生命周期与边界规则由 [initiator 作用域决策](../../.agents/notes/implemented/architecture/2026-07-15-agent-initiator-scope.md)规定。 - -## 拦截决策 - -提示词决策与工具后决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。每个 `additionalContexts` 条目都会成为一条独立的 `user/message`,保留各自的标识与 provenance。钩子桥接层把其原生决策字段映射到这些类型化结果上。 - -源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts) - -`agent/prompt-submit` 在轮次打开前返回 `PromptDecision`。allow 可以改写已领取的提示词或附加 `additionalContexts`;block 拒绝准入且不产生任何轮次事件: - -```ts type-equiv -/** - * Prompt interception result. `allow.content` replaces the prompt, while - * `additionalContexts` appends model-facing context before the turn starts. - * An `allow` returned by a listener is authoritative: a listener wrapping - * `next()` preserves both fields unless it intentionally replaces them. - */ -type PromptDecision = - | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessage[] } - | { kind: 'block'; reason: string } -``` - -`agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。 - -```ts type-equiv -/** Action returned by a listener that owns model-request recovery. */ -type RequestErrorAction = { kind: 'retry' } | undefined -``` - -```ts type-equiv -/** Model-request failure with an optional machine-routable provider code. */ -type RequestError = Error & { code?: string } -``` - -`agent/step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。 - -`agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart): - -```ts type-equiv -/** Why a session lifecycle began; seeded creates are `startup`, while persisted loads are `resume`. */ -type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact' -``` - -## `ToolDefinition` - -唯一属于核心的流水线编写类型:每个已注册工具*是什么*——一个面向模型的 `ToolSchema` 加上一个 `execute` 函数,以及可选的最终内容回调与 UI 回调。工具作者很少手动构造它(`defineTool` DSL 会用类型化参数构建),但它是注册表持有、循环分发所经过的契约。 - -其完整字段、`defineTool`/`ValueSchemaSpec`/`ParameterSchemaSpec` 类型化 schema DSL、`ToolExecution`/`ToolExecutionResult` waterfall 形状,以及工具展示 UI 词汇在 **[tools.md](tools.md)** 中。 - @@ -466,7 +359,7 @@ async resume(ownerCtx: Context, options: ResumeAgentOptions): Promise @@ -638,35 +531,12 @@ list(): Agent[] roots(): Agent[] ``` -Source: [`packages/core/agent/src/index.ts:242`](../../packages/core/agent/src/index.ts) +Source: [`packages/core/agent/src/index.ts:253`](../../packages/core/agent/src/index.ts) ### `agent/*` events - - -#### `agent/cancel-requested` — emit - -Effective broad cancellation was requested, before queued/outbox work is cleared or the active turn is aborted. This observe-only notification cannot veto cancellation; listener failures are contained. - -```ts cordis-catalog -/** - * Effective broad cancellation was requested, before queued/outbox work - * is cleared or the active turn is aborted. This observe-only notification - * cannot veto cancellation; listener failures are contained. - * @param agent - the agent whose current work is being cancelled. - * @param cause - the explicit typed cancellation cause. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/cancel-requested'(this: Scoped, agent: Agent, cause: AgentCancelCause): void -``` - -Types: [Scoped](scope.md) - -Source: [`packages/core/agent/src/types.ts:353`](../../packages/core/agent/src/types.ts) - #### `agent/created` — emit @@ -680,11 +550,11 @@ A fully configured agent and live session were published. Setup is composition-o * Synchronous listener failure vetoes publication, while returned-promise * rejection is reported. Detach requested during dispatch waits until every * creation listener has observed the stable entry. - * @param agent - the newly registered agent with its live session and completed setup. + * @param payload.agent - the newly registered agent with its live session and completed setup. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/created'(this: Scoped, agent: Agent): void +'agent/created'(this: Scoped, payload: { agent: Agent }): void ``` Types: [Scoped](scope.md) @@ -702,11 +572,11 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco * An agent left the registry; AgentLoop emits this after driver quiescence * and scoped-registration unwind, but before session detachment. Custom * registry users own their driver-ordering contract. - * @param agent - the exact agent removed from the registry. + * @param payload.agent - the exact agent removed from the registry. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/disposed'(this: Scoped, agent: Agent): void +'agent/disposed'(this: Scoped, payload: { agent: Agent }): void ``` Types: [Scoped](scope.md) @@ -717,138 +587,111 @@ Source: [`packages/core/agent/src/types.ts:167`](../../packages/core/agent/src/t #### `agent/error` — emit -A step or turn errored. The machine reports a failure here (plus the logger) even when the error has no in-turn position for a durable record. +A step or turn errored. The machine reports a failure here even when the error has no in-turn position for a durable record. ```ts cordis-catalog /** - * A step or turn errored. The machine reports a failure here (plus the - * logger) even when the error has no in-turn position for a durable record. - * @param agent - the agent whose turn errored. - * @param turn - the turn in which the failure surfaced. - * @param step - the step at which the failure surfaced. - * @param error - the failure, verbatim. + * A step or turn errored. The machine reports a failure here even when + * the error has no in-turn position for a durable record. + * @param payload.agent - the agent whose turn errored. + * @param payload.turn - the turn in which the failure surfaced. + * @param payload.step - the step at which the failure surfaced. + * @param payload.error - the failure, verbatim. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/error'(this: Scoped, agent: Agent, turn: number, step: number, error: unknown): void +'agent/error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; error: unknown }): void ``` Types: [Scoped](scope.md) Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/dequeue` — emit +#### `agent/inbox/claimed` — emit -The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message. +One message left the inbox inside its open turn. If the proposed step is rejected, the claimed message ends here: it is neither discarded nor re-emitted as a user/message, and the turn closes without a step. ```ts cordis-catalog /** - * The driver claimed one item out of the inbox: a queued item at a turn - * boundary, or steering drained between steps. Fires after the item leaves - * its FIFO and before it becomes a durable message. - * @param agent - the agent whose inbox item was claimed. - * @param item - the exact claimed occurrence. + * One message left the inbox inside its open turn. If the proposed step + * is rejected, the claimed message ends here: it is neither discarded nor + * re-emitted as a user/message, and the turn closes without a step. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the claimed message. + * @param payload.turn - the owning turn. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/dequeue'(this: Scoped, agent: Agent, item: InboxItem): void +'agent/inbox/claimed'(this: Scoped, payload: { agent: Agent; message: UserMessage; turn: number }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:196`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/discard` — emit +#### `agent/inbox/discarded` — emit -Pending inbox items were dropped without delivering them, so every enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, emits this after `agent/cancel-requested` when applicable and before aborting the active work. Fires once per drop with every dropped item. +One message was discarded from the live inbox. ```ts cordis-catalog /** - * Pending inbox items were dropped without delivering them, so every - * enqueue occurrence receives exactly one terminal `agent/inbox/dequeue` OR - * `agent/inbox/discard`. `cancel()` without `keepInbox`, including disposal, - * emits this after `agent/cancel-requested` when applicable and before - * aborting the active work. Fires once per drop with every dropped item. - * @param agent - the agent whose inbox items were dropped. - * @param items - the discarded occurrences in FIFO order (queued then steering); never empty. + * One message was discarded from the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the discarded message. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/discard'(this: Scoped, agent: Agent, items: InboxItem[]): void +'agent/inbox/discarded'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:204`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/enqueue` — emit +#### `agent/inbox/inserted` — emit -An item entered the queued or steering inbox. `placement` is the acceptance-time routing result; listeners must not reconstruct it from later agent or session state. +One message entered the live inbox. ```ts cordis-catalog /** - * An item entered the queued or steering inbox. `placement` is the - * acceptance-time routing result; listeners must not reconstruct it from - * later agent or session state. - * @param agent - the owning agent. - * @param item - accepted occurrence, message, and resolved placement. + * One message entered the live inbox. + * @param payload.agent - the agent whose inbox changed. + * @param payload.message - the inserted message. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/inbox/enqueue'(this: Scoped, agent: Agent, item: InboxItem): void +'agent/inbox/inserted'(this: Scoped, payload: { agent: Agent; message: UserMessage }): void ``` -Types: [Scoped](scope.md) +Types: [Scoped](scope.md) · [UserMessage](session.md) Source: [`packages/core/agent/src/types.ts:185`](../../packages/core/agent/src/types.ts) - + -#### `agent/inbox/update` — emit +#### `agent/pre-step` — waterfall -A still-pending queued item changed content. The item id, placement, and position remain stable while the event carries the replacement message. +Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages. ```ts cordis-catalog /** - * A still-pending queued item changed content. The item id, placement, and - * position remain stable while the event carries the replacement message. - * @param agent - the owning agent. - * @param item - the complete post-update occurrence. - * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. - * @mode emit - */ -'agent/inbox/update'(this: Scoped, agent: Agent, item: InboxItem): void -``` - -Types: [Scoped](scope.md) - -Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts) - - - -#### `agent/prompt-submit` — waterfall - -Allow, rewrite, or block one claimed prompt before it becomes a user message or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn. - -```ts cordis-catalog -/** - * Allow, rewrite, or block one claimed prompt before it becomes a user - * message or opens a turn. Call `next()` for the unchanged default. The - * signal controls only this admission attempt; listeners may cooperate with - * it but must not retain it for a later attempt or turn. - * @param agent - the agent whose turn claimed the message. - * @param message - the frozen claimed message, including identity and source. - * @param signal - the current turn's explicit abort signal. + * Reject a proposed step or replace the messages that enter it. Calling + * `next()` preserves the current messages. + * @param payload.agent - the agent proposing the step. + * @param payload.messages - messages removed from the inbox for this step. + * @param payload.turn - the turn that will own the step. + * @param payload.step - the step proposed by the loop. + * @param payload.signal - the current turn's cancellation signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/prompt-submit'(this: Scoped, agent: Agent, message: UserMessage, signal: AbortSignal, next: () => Promise): Promise +'agent/pre-step'(this: Scoped, payload: { agent: Agent; messages: UserMessage[]; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [Scoped](scope.md) · [UserMessage](session.md) @@ -867,14 +710,14 @@ Replace the frozen call configuration. `await next()` yields the config the mach * the machine would use (agent options on the first request, the logged * header afterwards); return a replacement to switch. Model-visible * content must use logged channels; this seam cannot mutate messages. - * @param agent - the agent making the model call. - * @param turn - the open turn number. - * @param step - the step whose request this is. - * @param signal - the current turn's explicit abort signal. + * @param payload.agent - the agent making the model call. + * @param payload.turn - the open turn number. + * @param payload.step - the step whose request this is. + * @param payload.signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request'(this: Scoped, agent: Agent, turn: number, step: number, signal: AbortSignal, next: () => Promise): Promise +'agent/request'(this: Scoped, payload: { agent: Agent; turn: number; step: number; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [LlmCallConfig](llm-streaming.md) · [Scoped](scope.md) @@ -885,28 +728,25 @@ Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/t #### `agent/request-error` — waterfall -Handle a model-request failure after its failed step has closed but before the failed turn closes. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns the error, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. +Handle one failed model-request attempt before the loop retries or closes its step. A listener returns `{ kind: 'retry' }` without calling `next()` when it owns recovery, or calls `next()` to delegate. The default `undefined` leaves the failure terminal. ```ts cordis-catalog /** - * Handle a model-request failure after its failed step has closed but - * before the failed turn closes. A listener returns `{ kind: 'retry' }` - * without calling `next()` when it owns the error, or calls `next()` to - * delegate. The default `undefined` leaves the failure terminal. - * @param agent - the agent whose request failed. - * @param turn - the open turn number. - * @param step - the failed step number. - * @param error - the original model-request failure. - * @param failure - serializable facts normalized at the final adapter boundary. - * @param priorFailures - immutable failures that already authorized another - * retry turn in this consecutive sequence. - * @param retryPolicy - immutable policy of the adapter registration that served - * the failed request, or `undefined` if no final adapter served it. - * @param signal - the turn abort signal. + * Handle one failed model-request attempt before the loop retries or closes + * its step. A listener returns `{ kind: 'retry' }` without calling `next()` + * when it owns recovery, or calls `next()` to delegate. The default + * `undefined` leaves the failure terminal. + * @param payload.agent - the agent whose request failed. + * @param payload.turn - the turn containing the failed request. + * @param payload.step - the step containing the failed request attempt. + * @param payload.provider - the provider selected for the failed request. + * @param payload.failure - serializable facts normalized at the final adapter boundary. + * @param payload.retryPolicy - the policy of the adapter registration that served the failed request. + * @param payload.signal - the turn abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode waterfall */ -'agent/request-error'(this: Scoped, agent: Agent, turn: number, step: number, error: RequestError, failure: LlmFailure, priorFailures: readonly LlmFailure[], retryPolicy: ResolvedRetryPolicy | undefined, signal: AbortSignal, next: () => Promise): Promise +'agent/request-error'(this: Scoped, payload: { agent: Agent; turn: number; step: number; provider: string; failure: LlmFailure; retryPolicy: ResolvedRetryPolicy | undefined; signal: AbortSignal }, next: () => Promise): Promise ``` Types: [LlmFailure](llm-streaming.md) · [ResolvedRetryPolicy](llm-streaming.md) · [Scoped](scope.md) @@ -925,12 +765,12 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to * `agent.inject()` to seed model-facing context. This is a notification, not * a veto; disposal requested by a lifecycle owner is rechecked before the * driver starts. - * @param agent - the agent whose session lifecycle began. - * @param source - why the session started (fresh startup, resume, …). + * @param payload.agent - the agent whose session lifecycle began. + * @param payload.source - why the session started (fresh startup, resume, …). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/session-start'(this: Scoped, agent: Agent, source: SessionStartSource): void +'agent/session-start'(this: Scoped, payload: { agent: Agent; source: SessionStartSource }): void ``` Types: [Scoped](scope.md) @@ -941,18 +781,19 @@ Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/t #### `agent/status` — emit -Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event. +Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running` synchronously after reserving cancellation; `idle` means no driver remains scheduled or active. ```ts cordis-catalog /** - * Agent status changed (`idle` ⇄ `running`). `send()` does not enter - * `running` synchronously; drive lifecycle from this event. - * @param agent - the agent whose status flipped. - * @param status - the status just entered (the transition's destination). + * Agent status changed (`idle` ⇄ `running`). A waking delivery enters + * `running` synchronously after reserving cancellation; `idle` means no + * driver remains scheduled or active. + * @param payload.agent - the agent whose status flipped. + * @param payload.status - the status just entered (the transition's destination). * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode emit */ -'agent/status'(this: Scoped, agent: Agent, status: AgentStatus): void +'agent/status'(this: Scoped, payload: { agent: Agent; status: AgentStatus }): void ``` Types: [Scoped](scope.md) @@ -963,7 +804,7 @@ Source: [`packages/core/agent/src/types.ts:177`](../../packages/core/agent/src/t #### `agent/turn-stopping` — serial -The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. +The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step. The conclusion never short-circuits already-submitted next-step work: same-step `additionalContexts` or racing steering still runs, and the turn closes only when that inbox drains. ```ts cordis-catalog /** @@ -973,14 +814,17 @@ The turn is about to close: the model owes no response (no live tool calls, no f * re-reads its inbox: fresh steering runs another step, none closes the * turn. Data decides, so listener order cannot change the outcome. The * inverse control (stop a tool loop early) is data too: a tool result - * carrying `concludesTurn` ends the turn at its step. - * @param agent - the agent whose turn is at its stop boundary. - * @param turn - the turn about to close. - * @param signal - the current turn's explicit abort signal. + * carrying `concludesTurn` ends the turn at its step. The conclusion + * never short-circuits already-submitted next-step work: same-step + * `additionalContexts` or racing steering still runs, and the turn + * closes only when that inbox drains. + * @param payload.agent - the agent whose turn is at its stop boundary. + * @param payload.turn - the turn about to close. + * @param payload.signal - the current turn's explicit abort signal. * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent. * @mode serial */ -'agent/turn-stopping'(this: Scoped, agent: Agent, turn: number, signal: AbortSignal): Promise | void +'agent/turn-stopping'(this: Scoped, payload: { agent: Agent; turn: number; signal: AbortSignal }): Promise | void ``` Types: [Scoped](scope.md) @@ -1003,12 +847,12 @@ A declarative agent entry failed before it could publish a live agent. Consumers * Consumers that buffer work for the configured identity use this * transient signal to reject that work instead of waiting forever. Normal * factory teardown suppresses failures from the cancelled startup attempt. - * @param sessionId - exact shared agent/session identity that failed startup. - * @param error - persistence, setup, or publication failure. + * @param payload.sessionId - exact shared agent/session identity that failed startup. + * @param payload.error - persistence, setup, or publication failure. * @mode emit */ -'agent-loop/config-start-failed'(sessionId: SessionId, error: unknown): void +'agent-loop/config-start-failed'(payload: { sessionId: SessionId; error: unknown }): void ``` -Source: [`packages/core/agent-loop/src/index.ts:157`](../../packages/core/agent-loop/src/index.ts) +Source: [`packages/core/agent-loop/src/index.ts:182`](../../packages/core/agent-loop/src/index.ts) diff --git a/docs/subsystems/http-server.i18n.yaml b/docs/subsystems/http-server.i18n.yaml index 3674a95c44..572039f635 100644 --- a/docs/subsystems/http-server.i18n.yaml +++ b/docs/subsystems/http-server.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/http-server.md http-server.md: b9795fe98b432b6ef5f7d01a4d3e115c809fe642 -http-server.zh.md: c3ba02dce5c58f491921f1fb31f999f9da808bc5 +http-server.zh.md: b84ea457bcd37eed361fde88fbf680d189aeebfb diff --git a/docs/subsystems/http-server.zh.md b/docs/subsystems/http-server.zh.md index c3ba02dce5..b84ea457bc 100644 --- a/docs/subsystems/http-server.zh.md +++ b/docs/subsystems/http-server.zh.md @@ -2,7 +2,7 @@ [English](http-server.md) | 中文 -[dsh-host-webserver](../../packages/host/webserver) 是 GUI 宿主 web 形态的 HTTP 载体:单个提供 `ctx.httpServer` 的 `node:http` 插件,由具名路由注册表加 index.html 转换挂点组成,兜底是静态 dist 回退。它不属于 agent loop(智能体循环)主干,也不是能力 seam:它不了解任何 harness 概念,每个功能表面(`/api` 桥接、插件 bundle、HMR(热模块替换)事件流)都是由其他插件注册的一条路由([分层说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md))。仅限 web(浏览器)形态:Electron 通过 `file://` 加载 dist,并经 IPC 桥接承载 fetch,不经过本服务器。 +[dsh-host-webserver](../../packages/host/webserver) 是 GUI 宿主 web 形态的 HTTP 载体:单个提供 `ctx.httpServer` 的 `node:http` 插件,由具名路由注册表、index.html 转换挂点与单一可认领的回退席位组成。它不属于 agent loop(智能体循环)主干,也不是能力 seam:它不了解任何 harness 概念,每个功能表面(`/api` 桥接、插件 bundle、HMR(热模块替换)事件流)都是由其他插件注册的一条路由([分层说明](../../.agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md))。仅限 web(浏览器)形态:Electron 通过 `file://` 加载 dist,并经 IPC 桥接承载 fetch,不经过本服务器。 源码:[`packages/host/webserver/src/index.ts`](../../packages/host/webserver/src/index.ts) diff --git a/docs/subsystems/llm-streaming.i18n.yaml b/docs/subsystems/llm-streaming.i18n.yaml index f6ce932ad9..f807656000 100644 --- a/docs/subsystems/llm-streaming.i18n.yaml +++ b/docs/subsystems/llm-streaming.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md -llm-streaming.md: b3f850f4d07ad9e36d0e0db71dd9e0498e468767 -llm-streaming.zh.md: bb31c14b9bd0453399d83968b1062f67819caca3 +llm-streaming.md: c65416f449d1783f398cdd81b0d6987de83a6141 +llm-streaming.zh.md: ce79ecca53edf7213853df118489817451baa7b0 diff --git a/docs/subsystems/llm-streaming.md b/docs/subsystems/llm-streaming.md index b3f850f4d0..c65416f449 100644 --- a/docs/subsystems/llm-streaming.md +++ b/docs/subsystems/llm-streaming.md @@ -72,12 +72,84 @@ Where a message came from is itself a merge-extensible sum type: */ interface MessageSourceMap { user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } + plugin: { kind: 'plugin'; plugin: string } & ContextFormed model: ModelMessageSource tool: ToolMessageSource } ``` +Provenance and shape are two independent axes. `kind` answers *who produced this*; the optional `form` a producer mixes in answers *what shape of information it is*, so several producers may share one presentation and one producer may emit more than one shape over a session. The vocabulary is semantic and grows one value at a time; an absent or unrecognized value is the documented default, presented as opaque content: + +```ts type-equiv +/** + * What SHAPE of information a producer-supplied context carries, declared by + * the producer beside its provenance. + * + * `MessageSource.kind` answers *who produced this*; `form` answers *what kind + * of thing it is*, and the two axes are deliberately independent — several + * producers share one form (three snapshot producers today), and one producer + * may emit more than one form over a session. + * + * The vocabulary is SEMANTIC, never visual: a value states that the content is + * a file's instructions or a catalog of available items, and a consumer decides + * what that looks like. Colors, icons, ordering, and collapse defaults are the + * consumer's business and must not enter this union. It grows one value at a + * time as producers gain the structured fields their form needs; an absent or + * unknown value is the documented default, presented as opaque content. + */ +type ContextForm = + /** Instructions read out of workspace files the model is expected to follow. */ + | 'instructions' + /** A catalog of items available in this session, republished as it changes. */ + | 'catalog' + /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ + | 'snapshot' + /** A one-off account of something that just happened; it supersedes nothing. */ + | 'notice' + /** A message another agent addressed to this one. */ + | 'relay' + /** Material lifted out of another session's log, possibly reduced on the way in. */ + | 'recall' +``` + +```ts type-equiv +/** One named contribution to a `snapshot`-form context, in assembly order. */ +interface ContextSnapshotSection { + /** The contributing subsystem's name. */ + readonly name: string + /** That contribution's model-facing text, exactly as assembled. */ + readonly text: string +} +``` + +```ts type-equiv +/** + * Producer-declared {@link ContextForm} and the fields that form requires, + * mixed into the source shapes that carry one. + * + * Discriminated by `form` so a producer cannot declare a shape without the + * facts that shape is presented from: a `notice` must record its one-line + * account, a `snapshot` its sections. Omitting `form` stays valid — an + * undeclared context is the documented default. + */ +type ContextFormed = + | { readonly form?: never } + | { readonly form: 'instructions' } + | { readonly form: 'catalog' } + | { + readonly form: 'snapshot' + /** The named contributions this snapshot assembled, in order. */ + readonly sections: readonly ContextSnapshotSection[] + } + | { + readonly form: 'notice' + /** One-line account of what happened, shown without expanding the row. */ + readonly summary: string + } + | { readonly form: 'relay' } + | { readonly form: 'recall' } +``` + ## `StreamChunk` — the raw protocol A streaming response interleaves several typed blocks (text, reasoning, multiple tool calls). `index` ties each delta to its block; `block-end` carries the fully-assembled `ContentBlock` so consumers don't have to re-assemble deltas themselves. It is a **closed** discriminated union — a `switch` over `type` ends with `assertNever`, so adding a variant breaks compilation at every consumer that must handle it. @@ -87,8 +159,9 @@ A streaming response interleaves several typed blocks (text, reasoning, multiple * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. Failures either throw or - * end with `error`/`aborted`, and consumers must handle both paths. + * afterward; tool arguments remain raw JSON strings. An adapter implementation + * may throw, but `LlmService.stream()` normalizes that failure to a terminal + * `error` or `aborted` finish before exposing it to consumers. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -213,8 +286,9 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index; an open block assembles from its - * accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index, except that max-token truncation drops + * tool calls that cannot be executed safely; an open block assembles from + * its accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -301,6 +375,15 @@ interface LlmConfigurableProvider { * object; empty when the whole section is the profile. */ settingsPath: readonly string[] + /** + * Whether the owning adapter knows this route only because configuration + * declared it — a gateway or self-hosted server it ships nothing about. + * Absent means the adapter draws no such distinction; false means it does + * and this route is one of its own. Only the adapter can answer: a stored + * profile is how a user-added route AND a corrected shipped one both look + * from outside. + */ + declared?: boolean } ``` @@ -451,6 +534,55 @@ interface ToolSchema { The model-facing `ToolSchema` is the wire shape; the registered `ToolDefinition` that produces it (schema + `execute`) is on [tools.md](tools.md). +A provider a surface is still drafting has no route and no catalog, so interrogation is described separately: the request carries the draft the user is editing, and the reply is candidates a surface may adopt rather than a catalog it must serve. + +```ts type-equiv +/** + * One interrogation of a provider endpoint that configuration has not stored + * yet. Configuration surfaces send the draft a user is still editing, so the + * request carries the endpoint and credential directly instead of naming a + * route: a provider being added has no route to name. + */ +interface LlmModelDiscoveryRequest { + /** + * Route the draft is editing, when it edits an existing one. A route whose + * adapter already knows its models answers from that knowledge instead of + * asking the endpoint — the adapter's own registry is the better answer, and + * it costs no network call. + */ + provider?: string + /** + * Endpoint to interrogate. Optional because a route the adapter already + * describes needs none; a route it does not must supply one. + */ + baseURL?: string + /** Wire protocol the endpoint speaks, when the draft names one. */ + api?: string + /** Credential for this interrogation alone; the harness never stores it. */ + apiKey?: string + /** Caller cancellation; implementations must settle promptly after it aborts. */ + signal?: AbortSignal +} +``` + +```ts type-equiv +/** + * One model an endpoint reports about itself. Every field but the id is + * optional because most provider listings disclose an id and nothing else; + * a surface adopting one of these still owes the capacities its adapter needs. + */ +interface LlmDiscoveredModel { + /** Model id the endpoint accepts. */ + id: string + /** Human-readable name when the endpoint supplies one. */ + name?: string + /** Maximum combined request and response context, when disclosed. */ + contextWindow?: number + /** Maximum output tokens, when disclosed. */ + maxTokens?: number +} +``` + ### The request envelope: `LlmCallConfig` and the logged header The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md). @@ -498,6 +630,8 @@ interface LlmCallConfigAdapterDefaults { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig + /** Immutable retry policy captured with the adapter registration. */ + readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -603,9 +737,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns the disposer that withdraws all of them. + * @returns a handle that withdraws all of them, and can atomically replace them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle /** * List every declared configurable provider, registered or dormant. @@ -613,6 +747,29 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () = */ listConfigurableProviders(): LlmConfigurableProvider[] +/** + * Offer to interrogate provider endpoints on behalf of the settings + * namespace this plugin owns. The namespace is the key because that is what + * a configuration surface already holds from the configurable-provider + * directory, and because a provider being *added* has no route to name yet. + * Disposed with the fiber. + * @param settingsNs - the namespace whose profiles this discovery serves. + * @param discover - interrogates one endpoint; must honor `request.signal`. + * @returns the disposer that withdraws the offer. + */ +registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void + +/** + * Interrogate one provider endpoint for the models it advertises. The + * request describes a draft, not a stored route, so nothing here reads or + * writes settings or credentials — the caller owns both, and the reply is + * candidate metadata a surface may offer for adoption. + * @param settingsNs - namespace whose registered discovery serves this draft. + * @param request - the endpoint, protocol, and one-shot credential to use. + * @returns the advertised models, deduplicated in endpoint order. + */ +async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise + /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -662,22 +819,20 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Throws - * `LlmError` with code `NO_ADAPTER` if no adapter is registered for - * `options.provider`. Replay state is retained only when the same adapter - * instance owns its historical provider and the target provider. Final - * adapter selection remains fixed through asynchronous exact-model resolution - * and dispatch. Selection, dispatch, and iteration failures retain their - * original Error identity and are tagged in a call-local scope for narrow - * agent-loop request recovery; middleware and nested-call failures remain - * untagged for the outer call. + * Stream one model call as raw chunks (token-level deltas). Replay state is + * retained only when the same adapter instance owns its historical provider + * and the target provider. Final adapter selection remains fixed through + * asynchronous exact-model resolution and dispatch. Adapter selection, + * dispatch, and iteration failures become terminal `error` or `aborted` + * finish chunks; middleware, nested-call, cleanup, and consumer failures + * remain thrown. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) @@ -702,7 +857,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) @@ -726,5 +881,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/llm-streaming.zh.md b/docs/subsystems/llm-streaming.zh.md index bb31c14b9b..ce79ecca53 100644 --- a/docs/subsystems/llm-streaming.zh.md +++ b/docs/subsystems/llm-streaming.zh.md @@ -72,12 +72,84 @@ interface Message { */ interface MessageSourceMap { user: { kind: 'user' } - plugin: { kind: 'plugin'; plugin: string } + plugin: { kind: 'plugin'; plugin: string } & ContextFormed model: ModelMessageSource tool: ToolMessageSource } ``` +溯源与形态是相互独立的两根轴。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现: + +```ts type-equiv +/** + * What SHAPE of information a producer-supplied context carries, declared by + * the producer beside its provenance. + * + * `MessageSource.kind` answers *who produced this*; `form` answers *what kind + * of thing it is*, and the two axes are deliberately independent — several + * producers share one form (three snapshot producers today), and one producer + * may emit more than one form over a session. + * + * The vocabulary is SEMANTIC, never visual: a value states that the content is + * a file's instructions or a catalog of available items, and a consumer decides + * what that looks like. Colors, icons, ordering, and collapse defaults are the + * consumer's business and must not enter this union. It grows one value at a + * time as producers gain the structured fields their form needs; an absent or + * unknown value is the documented default, presented as opaque content. + */ +type ContextForm = + /** Instructions read out of workspace files the model is expected to follow. */ + | 'instructions' + /** A catalog of items available in this session, republished as it changes. */ + | 'catalog' + /** Current state, where a later snapshot from the same producer supersedes an earlier one. */ + | 'snapshot' + /** A one-off account of something that just happened; it supersedes nothing. */ + | 'notice' + /** A message another agent addressed to this one. */ + | 'relay' + /** Material lifted out of another session's log, possibly reduced on the way in. */ + | 'recall' +``` + +```ts type-equiv +/** One named contribution to a `snapshot`-form context, in assembly order. */ +interface ContextSnapshotSection { + /** The contributing subsystem's name. */ + readonly name: string + /** That contribution's model-facing text, exactly as assembled. */ + readonly text: string +} +``` + +```ts type-equiv +/** + * Producer-declared {@link ContextForm} and the fields that form requires, + * mixed into the source shapes that carry one. + * + * Discriminated by `form` so a producer cannot declare a shape without the + * facts that shape is presented from: a `notice` must record its one-line + * account, a `snapshot` its sections. Omitting `form` stays valid — an + * undeclared context is the documented default. + */ +type ContextFormed = + | { readonly form?: never } + | { readonly form: 'instructions' } + | { readonly form: 'catalog' } + | { + readonly form: 'snapshot' + /** The named contributions this snapshot assembled, in order. */ + readonly sections: readonly ContextSnapshotSection[] + } + | { + readonly form: 'notice' + /** One-line account of what happened, shown without expanding the row. */ + readonly summary: string + } + | { readonly form: 'relay' } + | { readonly form: 'recall' } +``` + ## `StreamChunk`:原始协议 @@ -89,8 +161,9 @@ interface MessageSourceMap { * Raw streaming protocol emitted by adapters. * Block indexes correlate interleaved deltas, and `block-end` carries the * assembled block. Adapters emit usage before the terminal finish and nothing - * afterward; tool arguments remain raw JSON strings. Failures either throw or - * end with `error`/`aborted`, and consumers must handle both paths. + * afterward; tool arguments remain raw JSON strings. An adapter implementation + * may throw, but `LlmService.stream()` normalizes that failure to a terminal + * `error` or `aborted` finish before exposing it to consumers. */ type StreamChunk = | { type: 'block-start'; index: number; blockType: ContentBlockType } @@ -221,8 +294,9 @@ declare class BlockAssembler { push(chunk: StreamChunk): void; /** * Assemble all blocks seen so far, in stream order. - * @returns one block per seen index; an open block assembles from its - * accumulated deltas (an unknown block type never closed by `block-end` throws). + * @returns one block per seen index, except that max-token truncation drops + * tool calls that cannot be executed safely; an open block assembles from + * its accumulated deltas (an unknown block type never closed by `block-end` throws). */ blocks(): ContentBlock[]; /** Usage from the `usage` chunk; undefined until one arrives. */ @@ -309,6 +383,15 @@ interface LlmConfigurableProvider { * object; empty when the whole section is the profile. */ settingsPath: readonly string[] + /** + * Whether the owning adapter knows this route only because configuration + * declared it — a gateway or self-hosted server it ships nothing about. + * Absent means the adapter draws no such distinction; false means it does + * and this route is one of its own. Only the adapter can answer: a stored + * profile is how a user-added route AND a corrected shipped one both look + * from outside. + */ + declared?: boolean } ``` @@ -459,6 +542,55 @@ interface ToolSchema { 面向模型的 `ToolSchema` 是协议格式;产出它的已注册 `ToolDefinition`(schema + `execute`)在 [tools.md](tools.md) 中。 +界面正在起草的提供方既没有路由也没有 catalog,因此询问被单独描述:请求携带用户正在编辑的草稿,回复是界面可以采纳的候选,而不是它必须服务的 catalog。 + +```ts type-equiv +/** + * One interrogation of a provider endpoint that configuration has not stored + * yet. Configuration surfaces send the draft a user is still editing, so the + * request carries the endpoint and credential directly instead of naming a + * route: a provider being added has no route to name. + */ +interface LlmModelDiscoveryRequest { + /** + * Route the draft is editing, when it edits an existing one. A route whose + * adapter already knows its models answers from that knowledge instead of + * asking the endpoint — the adapter's own registry is the better answer, and + * it costs no network call. + */ + provider?: string + /** + * Endpoint to interrogate. Optional because a route the adapter already + * describes needs none; a route it does not must supply one. + */ + baseURL?: string + /** Wire protocol the endpoint speaks, when the draft names one. */ + api?: string + /** Credential for this interrogation alone; the harness never stores it. */ + apiKey?: string + /** Caller cancellation; implementations must settle promptly after it aborts. */ + signal?: AbortSignal +} +``` + +```ts type-equiv +/** + * One model an endpoint reports about itself. Every field but the id is + * optional because most provider listings disclose an id and nothing else; + * a surface adopting one of these still owes the capacities its adapter needs. + */ +interface LlmDiscoveredModel { + /** Model id the endpoint accepts. */ + id: string + /** Human-readable name when the endpoint supplies one. */ + name?: string + /** Maximum combined request and response context, when disclosed. */ + contextWindow?: number + /** Maximum output tokens, when disclosed. */ + maxTokens?: number +} +``` + ### 请求信封:`LlmCallConfig` 与记录的 header 循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。 @@ -506,6 +638,8 @@ interface LlmCallConfigAdapterDefaults { interface PreparedLlmCall { /** Detached, deep-frozen config with any adapter-owned default materialized. */ readonly config: LlmCallConfig + /** Immutable retry policy captured with the adapter registration. */ + readonly retryPolicy: ResolvedRetryPolicy /** Detached context metadata resolved with the registration-bound call. */ readonly context?: LlmModelContext /** Config fields materialized by the captured adapter rather than proposed by the caller. */ @@ -611,9 +745,9 @@ listProviders(): LlmProviderInfo[] * entry, or a provider already declared by any registration throws * `LlmError` without registering the rest. Disposed with the fiber. * @param entries - every configurable provider this plugin owns. - * @returns the disposer that withdraws all of them. + * @returns a handle that withdraws all of them, and can atomically replace them. */ -registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () => void +registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): DirectoryRegistrationHandle /** * List every declared configurable provider, registered or dormant. @@ -621,6 +755,29 @@ registerConfigurableProviders(entries: readonly LlmConfigurableProvider[]): () = */ listConfigurableProviders(): LlmConfigurableProvider[] +/** + * Offer to interrogate provider endpoints on behalf of the settings + * namespace this plugin owns. The namespace is the key because that is what + * a configuration surface already holds from the configurable-provider + * directory, and because a provider being *added* has no route to name yet. + * Disposed with the fiber. + * @param settingsNs - the namespace whose profiles this discovery serves. + * @param discover - interrogates one endpoint; must honor `request.signal`. + * @returns the disposer that withdraws the offer. + */ +registerModelDiscovery( settingsNs: string, discover: (request: LlmModelDiscoveryRequest) => Promise, ): () => void + +/** + * Interrogate one provider endpoint for the models it advertises. The + * request describes a draft, not a stored route, so nothing here reads or + * writes settings or credentials — the caller owns both, and the reply is + * candidate metadata a surface may offer for adoption. + * @param settingsNs - namespace whose registered discovery serves this draft. + * @param request - the endpoint, protocol, and one-shot credential to use. + * @returns the advertised models, deduplicated in endpoint order. + */ +async discoverModels( settingsNs: string, request: LlmModelDiscoveryRequest, ): Promise + /** * Resolve the retry policy captured when one provider route was registered. * @param provider - registered provider route to inspect. @@ -670,22 +827,20 @@ async resolveCallConfig(config: LlmCallConfig, signal?: AbortSignal): Promise /** - * Stream one model call as raw chunks (token-level deltas). Throws - * `LlmError` with code `NO_ADAPTER` if no adapter is registered for - * `options.provider`. Replay state is retained only when the same adapter - * instance owns its historical provider and the target provider. Final - * adapter selection remains fixed through asynchronous exact-model resolution - * and dispatch. Selection, dispatch, and iteration failures retain their - * original Error identity and are tagged in a call-local scope for narrow - * agent-loop request recovery; middleware and nested-call failures remain - * untagged for the outer call. + * Stream one model call as raw chunks (token-level deltas). Replay state is + * retained only when the same adapter instance owns its historical provider + * and the target provider. Final adapter selection remains fixed through + * asynchronous exact-model resolution and dispatch. Adapter selection, + * dispatch, and iteration failures become terminal `error` or `aborted` + * finish chunks; middleware, nested-call, cleanup, and consumer failures + * remain thrown. * @param options - the full request; `options.provider` selects the adapter. * @returns the chunk stream, possibly wrapped by `llm/stream` listeners. */ stream(options: GenerateOptions): AsyncIterable ``` -Source: [`packages/llm/llm/src/index.ts:232`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:292`](../../packages/llm/llm/src/index.ts) @@ -710,7 +865,7 @@ The provider topology changed: an adapter registered or unregistered routes, or 'llm/adapters-updated'(): void ``` -Source: [`packages/llm/llm/src/index.ts:71`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:73`](../../packages/llm/llm/src/index.ts) @@ -734,5 +889,5 @@ Waterfall around every streaming model call (retry, replay, routing). Bound to t 'llm/stream'(this: LlmService, options: GenerateOptions, next: () => AsyncIterable): AsyncIterable ``` -Source: [`packages/llm/llm/src/index.ts:60`](../../packages/llm/llm/src/index.ts) +Source: [`packages/llm/llm/src/index.ts:62`](../../packages/llm/llm/src/index.ts) diff --git a/docs/subsystems/permission.i18n.yaml b/docs/subsystems/permission.i18n.yaml index 6f6ba3b3c3..3f9c9b533c 100644 --- a/docs/subsystems/permission.i18n.yaml +++ b/docs/subsystems/permission.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/permission.md -permission.md: 60ed88a79d0e51922f4cdffafa8b547669b49f5d -permission.zh.md: c06714fc6a111f56af680c3dea1764c90f09998d +permission.md: 6f9a37dbb784ccbaea74056519992e40f9c07c6d +permission.zh.md: 8eda50fae09a67c41003d7a914fb139c4132a900 diff --git a/docs/subsystems/permission.md b/docs/subsystems/permission.md index 60ed88a79d..6f9a37dbb7 100644 --- a/docs/subsystems/permission.md +++ b/docs/subsystems/permission.md @@ -125,7 +125,7 @@ optionOf(name: string): PresetOption set(session: Session, name: string): void ``` -Types: [Session](session.md) · [SessionEvent](core.md) +Types: [Session](session.md) · [SessionEvent](session.md) Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts) diff --git a/docs/subsystems/permission.zh.md b/docs/subsystems/permission.zh.md index c06714fc6a..8eda50fae0 100644 --- a/docs/subsystems/permission.zh.md +++ b/docs/subsystems/permission.zh.md @@ -125,7 +125,7 @@ optionOf(name: string): PresetOption set(session: Session, name: string): void ``` -Types: [Session](session.md) · [SessionEvent](core.md) +Types: [Session](session.md) · [SessionEvent](session.md) Source: [`packages/interaction/permission/src/index.ts:159`](../../packages/interaction/permission/src/index.ts) diff --git a/docs/subsystems/persistence.i18n.yaml b/docs/subsystems/persistence.i18n.yaml index 808abb402a..295e5392ca 100644 --- a/docs/subsystems/persistence.i18n.yaml +++ b/docs/subsystems/persistence.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/persistence.md -persistence.md: 1edf3c88fbabe55058bbf94ed8f31decf47cb84a -persistence.zh.md: dbebd9ff819c8d50cfa9748678dac09c86adde6d +persistence.md: 580ec250ebad3f2b51982f98a611268f26487321 +persistence.zh.md: 6676bf26e6eb0fb8f7dd9a94d17d4d70d1d1b271 diff --git a/docs/subsystems/persistence.md b/docs/subsystems/persistence.md index 1edf3c88fb..580ec250eb 100644 --- a/docs/subsystems/persistence.md +++ b/docs/subsystems/persistence.md @@ -332,7 +332,7 @@ abstract list(signal?: AbortSignal): Promise abstract listSnapshots(signal?: AbortSignal): Promise ``` -Types: [SessionEvent](core.md) · [SessionId](core.md) +Types: [SessionEvent](session.md) · [SessionId](core.md) Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) diff --git a/docs/subsystems/persistence.zh.md b/docs/subsystems/persistence.zh.md index dbebd9ff81..6676bf26e6 100644 --- a/docs/subsystems/persistence.zh.md +++ b/docs/subsystems/persistence.zh.md @@ -332,7 +332,7 @@ abstract list(signal?: AbortSignal): Promise abstract listSnapshots(signal?: AbortSignal): Promise ``` -Types: [SessionEvent](core.md) · [SessionId](core.md) +Types: [SessionEvent](session.md) · [SessionId](core.md) Source: [`packages/session/session-persistence/src/index.ts:72`](../../packages/session/session-persistence/src/index.ts) diff --git a/docs/subsystems/session-projection.i18n.yaml b/docs/subsystems/session-projection.i18n.yaml index 24b1c7b234..fc603d658d 100644 --- a/docs/subsystems/session-projection.i18n.yaml +++ b/docs/subsystems/session-projection.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md -session-projection.md: 56f9fb43dddc482264aab52ceeb9b9920df148cd -session-projection.zh.md: 4a2310fed4af6f018af07b5f2b46d3c28aa361a4 +session-projection.md: 64351195028c62f623b32cbeb80c952795c1e9e6 +session-projection.zh.md: cf9036e6db3f82d84ffa3b8aa809c5617409659a diff --git a/docs/subsystems/session-projection.md b/docs/subsystems/session-projection.md index 56f9fb43dd..6435119502 100644 --- a/docs/subsystems/session-projection.md +++ b/docs/subsystems/session-projection.md @@ -256,7 +256,7 @@ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` -Types: [Session](session.md) · [SessionEvent](core.md) +Types: [Session](session.md) · [SessionEvent](session.md) Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) diff --git a/docs/subsystems/session-projection.zh.md b/docs/subsystems/session-projection.zh.md index 4a2310fed4..cf9036e6db 100644 --- a/docs/subsystems/session-projection.zh.md +++ b/docs/subsystems/session-projection.zh.md @@ -256,7 +256,7 @@ viewCheckpoint(checkpoint: ProjectionCheckpoint): Partial restore(checkpoint: ProjectionCheckpoint, events: readonly SessionEvent[], baseSeq: number): { snapshot: ProjectionSnapshot; checkpoint: ProjectionCheckpoint } ``` -Types: [Session](session.md) · [SessionEvent](core.md) +Types: [Session](session.md) · [SessionEvent](session.md) Source: [`packages/session/session-projection/src/index.ts:156`](../../packages/session/session-projection/src/index.ts) diff --git a/docs/subsystems/session.i18n.yaml b/docs/subsystems/session.i18n.yaml index a7e81056e5..2389cc806c 100644 --- a/docs/subsystems/session.i18n.yaml +++ b/docs/subsystems/session.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/session.md -session.md: 28b3f959b47a24373860c2334f9273286d072c95 -session.zh.md: 5870265f55892231700b65ac6ed69d827a0ab1ec +session.md: 7735e90807f6fce1b3645096437b3e86189dc8ec +session.zh.md: 09f34ac19c2f685bf5f8e5b11355b4c642c584d0 diff --git a/docs/subsystems/session.md b/docs/subsystems/session.md index 28b3f959b4..7735e90807 100644 --- a/docs/subsystems/session.md +++ b/docs/subsystems/session.md @@ -804,7 +804,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before 'session/event'(this: Scoped, session: Session, event: SessionEvent): void ``` -Types: [Scoped](scope.md) · [SessionEvent](core.md) +Types: [Scoped](scope.md) Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) diff --git a/docs/subsystems/session.zh.md b/docs/subsystems/session.zh.md index 5870265f55..09f34ac19c 100644 --- a/docs/subsystems/session.zh.md +++ b/docs/subsystems/session.zh.md @@ -808,7 +808,7 @@ Post-commit, fire-and-forget append feed. The listener snapshot resolves before 'session/event'(this: Scoped, session: Session, event: SessionEvent): void ``` -Types: [Scoped](scope.md) · [SessionEvent](core.md) +Types: [Scoped](scope.md) Source: [`packages/core/session/src/index.ts:96`](../../packages/core/session/src/index.ts) diff --git a/docs/subsystems/subagent.i18n.yaml b/docs/subsystems/subagent.i18n.yaml index a58335b9f1..994c04eaa5 100644 --- a/docs/subsystems/subagent.i18n.yaml +++ b/docs/subsystems/subagent.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/subagent.md -subagent.md: c132c073459642b133e0d7e662ee98a8c9dfb694 -subagent.zh.md: e46f9aa98389842918ae37dda1477ef1b565e774 +subagent.md: 3bf3ac7c48bfa08be86314bb43263d4ee6198654 +subagent.zh.md: ddc3a715fbb6589dffc38f0e0c8b23bf35dddf6e diff --git a/docs/subsystems/subagent.md b/docs/subsystems/subagent.md index c132c07345..3bf3ac7c48 100644 --- a/docs/subsystems/subagent.md +++ b/docs/subsystems/subagent.md @@ -133,7 +133,7 @@ persisted Session | `waiting` | wake the same Activation | | no Activation | cold-resume a new Activation | -`running` means the Agent has an active admission or turn, or waking inbox work; `waiting` means it is quiescent but still owns at least one child Activation that has not completed disposal; `settled` means quiescent with every owned child disposed, at which point the manager disposes the `AgentHandle` and removes the Activation. The manager derives these internal conditions from Agent quiescence and the owned-child set rather than maintaining a second execution state machine. +`running` means the Agent has an active admission or turn, or waking inbox work; `waiting` means it is quiescent but still owns at least one child Activation that has not completed disposal; `settled` means quiescent with every owned child disposed, at which point the manager disposes the [`AgentHandle`](core.md#creation-and-ownership) and removes the Activation. The manager derives these internal conditions from Agent quiescence and the owned-child set rather than maintaining a second execution state machine. The Agent inbox is the only queue. Every continuation message becomes one `Agent.followup()` FIFO turn, so accepted messages have one observable order and a follow-up cannot redirect a turn already underway. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/enqueue`, `agent/inbox/dequeue`, and `agent/inbox/discard` events remain the message-lifecycle observations, and the continuation layer defines no subagent-specific delivery route. @@ -432,7 +432,7 @@ Provider `start()` fulfills with a published run. The service mints a unique `ru The spawn and fork backends create an ordinary one-shot agent through `parent.ctx`, pass cancellation into core creation, and dispose through `AgentHandle`; a continuable child is instead created by the continuation manager through its own activation-owner scope. Provider removal blocks new starts without revoking accepted runs. Each child gets a new flat scope rather than inheriting parent registrations. Depth and fork seeding reuse existing agent and session vocabulary: - **Delegation depth** is durable `SessionHeader.delegationDepth` plus the merge-extensible runtime field `AgentOptions.subagentDepth`; absence means top-level depth zero, and the greater present value is authoritative. The seam owns both fields — the loop neither sets nor reads them — so an in-process child persists parent depth + 1, cold resume cannot lower it, and every start rejects a derived depth outside the safe-integer domain or above a defined absolute `request.maxDepth` cap. -- **Fork seeding** uses `CreateAgentOptions.seed` (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded). +- **Fork seeding** uses [`CreateAgentOptions.seed`](core.md#creation-and-ownership) (a `SessionEvent[]` prefix threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`, the same primitive `ctx.agents.resume()` uses). The fork backend passes a *balanced completed-turn prefix* of the parent's log — the parent's events up to and including its last `turn/end` — so the seed is contiguous-from-0 and the [invariants](../../packages/support/invariants) replay accepts it (the in-flight, unbalanced turn is excluded). diff --git a/docs/subsystems/subagent.zh.md b/docs/subsystems/subagent.zh.md index e46f9aa983..ddc3a715fb 100644 --- a/docs/subsystems/subagent.zh.md +++ b/docs/subsystems/subagent.zh.md @@ -133,7 +133,7 @@ persisted Session | `waiting` | 唤醒同一 Activation | | 无 Activation | 冷恢复一个新的 Activation | -`running` 表示 Agent 拥有活跃的准入或轮次,或正在唤醒收件箱工作;`waiting` 表示它已停稳,但仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示已停稳且其拥有的每个子级都已 dispose,此时管理器会 dispose `AgentHandle` 并移除该 Activation。管理器根据 Agent 的完全停稳状态与其拥有的子级集合推导这些内部条件,而非维护第二套执行状态机。 +`running` 表示 Agent 拥有活跃的准入或轮次,或正在唤醒收件箱工作;`waiting` 表示它已停稳,但仍拥有至少一个尚未完成 dispose 的子 Activation;`settled` 表示已停稳且其拥有的每个子级都已 dispose,此时管理器会 dispose [`AgentHandle`](core.md#creation-and-ownership) 并移除该 Activation。管理器根据 Agent 的完全停稳状态与其拥有的子级集合推导这些内部条件,而非维护第二套执行状态机。 Agent 收件箱是唯一的队列。每条继续执行消息都会成为一个 `Agent.followup()` FIFO 轮次,因此已接受的消息共享同一个可观测顺序,且后续消息无法改变已在进行中的轮次。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/enqueue`、`agent/inbox/dequeue` 与 `agent/inbox/discard` 事件仍是消息生命周期的观测点,继续执行层不定义任何 subagent 专属的投递路由。 @@ -434,7 +434,7 @@ interface SubagentProvider { spawn 和 fork 后端通过 `parent.ctx` 创建一个普通的单次 agent,将取消信号传入核心创建流程,并通过 `AgentHandle` 进行 dispose;而可继续子 agent 则由继续执行管理器通过其自己的 activation-owner 作用域创建。移除提供方会阻止新的 start,但不会撤销已接受的 run。每个子 agent 获得一个新的扁平作用域,而非继承父级注册。深度与 fork 种子注入复用既有的 agent 和会话词汇: - **委派深度**由持久 `SessionHeader.delegationDepth` 与可合并扩展的运行时字段 `AgentOptions.subagentDepth` 共同表示;缺失表示顶层深度为零,存在的较大值具有权威性。两个字段都归该 seam 所有——循环既不设置也不读取它们——因此进程内子 agent 会持久保存 parent 深度 + 1,冷恢复无法降低深度,而且每次 start 都会拒绝超出安全整数域、或高于已定义绝对 `request.maxDepth` 上限的派生深度。 -- **Fork 种子注入**使用 `CreateAgentOptions.seed`(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/support/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。 +- **Fork 种子注入**使用 [`CreateAgentOptions.seed`](core.md#creation-and-ownership)(一个 `SessionEvent[]` 前缀,经由 `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })` 传递,与 `ctx.agents.resume()` 使用的原语相同)。fork 后端传入父级日志的一段*平衡的已完成轮次前缀*——父级事件直到并包括其最后一个 `turn/end`——因此种子从 0 连续,[invariants](../../packages/support/invariants) 回放可以接受它(进行中的、未平衡的轮次被排除在外)。 diff --git a/docs/subsystems/tools.i18n.yaml b/docs/subsystems/tools.i18n.yaml index 003e175399..21003bb796 100644 --- a/docs/subsystems/tools.i18n.yaml +++ b/docs/subsystems/tools.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/tools.md -tools.md: b63014d1c2058c8fc7e96817d4bca858a1954482 -tools.zh.md: 2dc5802efce25a732a60c660191cd584ede623bb +tools.md: 0551ee1c9dfa12b415c1f4575b5225f771cb53bc +tools.zh.md: 50843b07f1d98b1c9d9222288b1273b34bee1e4a diff --git a/docs/subsystems/tools.md b/docs/subsystems/tools.md index b63014d1c2..0551ee1c9d 100644 --- a/docs/subsystems/tools.md +++ b/docs/subsystems/tools.md @@ -2,7 +2,7 @@ English | [中文](tools.zh.md) -The tool pipeline of [dsh-tools](../../packages/core/tools). [core.md](core.md) introduces `ToolDefinition` as the one pipeline-authoring type promoted to the spine and `ToolSchema` as the model-facing wire shape. This page owns the full `ToolDefinition`, the typed schema DSL that builds it, the guarded execution shapes, and the UI-presentation vocabulary. +The tool pipeline of [dsh-tools](../../packages/core/tools). [core.md](core.md) introduces `ToolDefinition` as the one pipeline-authoring type promoted to the spine; the model-facing [`ToolSchema`](llm-streaming.md#the-model-request-and-result) wire shape is declared with the model request. This page owns the full `ToolDefinition`, the typed schema DSL that builds it, the guarded execution shapes, and the UI-presentation vocabulary. Source: [`packages/core/tools/src/index.ts`](../../packages/core/tools/src/index.ts) · [`packages/core/tools/src/schema.ts`](../../packages/core/tools/src/schema.ts) · [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts) diff --git a/docs/subsystems/tools.zh.md b/docs/subsystems/tools.zh.md index 2dc5802efc..50843b07f1 100644 --- a/docs/subsystems/tools.zh.md +++ b/docs/subsystems/tools.zh.md @@ -2,7 +2,7 @@ [English](tools.md) | 中文 -[dsh-tools](../../packages/core/tools) 的工具流水线。[core.md](core.md) 介绍了 `ToolDefinition`(唯一被提升到主干的流水线编写类型)和 `ToolSchema`(面向模型的协议格式(wire format)形状)。本页拥有完整的 `ToolDefinition`、用于构建它的类型化 schema DSL、受保护的执行形状,以及 UI 展示词汇。 +[dsh-tools](../../packages/core/tools) 的工具流水线。[core.md](core.md) 介绍了 `ToolDefinition`(唯一被提升到主干的流水线编写类型);面向模型的 [`ToolSchema`](llm-streaming.md#the-model-request-and-result) 协议格式(wire format)形状与模型请求一起声明。本页拥有完整的 `ToolDefinition`、用于构建它的类型化 schema DSL、受保护的执行形状,以及 UI 展示词汇。 源码:[`packages/core/tools/src/index.ts`](../../packages/core/tools/src/index.ts) · [`packages/core/tools/src/schema.ts`](../../packages/core/tools/src/schema.ts) · [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts) diff --git a/packages/interaction/README.i18n.yaml b/packages/interaction/README.i18n.yaml index 77539b60fb..f2321e732a 100644 --- a/packages/interaction/README.i18n.yaml +++ b/packages/interaction/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/interaction/README.md -README.md: 31a306cb08fe69297b0d0843b57b5e491c0bbe44 -README.zh.md: 6eafe8726ba59edd3a6bf739deacc037a9b56b76 +README.md: a72cd48bb1d3ec15b1116d576fac86619df59528 +README.zh.md: 170a557e40befaa0f7073f25e8a2b372958a6ec1 diff --git a/packages/interaction/README.md b/packages/interaction/README.md index 31a306cb08..a72cd48bb1 100644 --- a/packages/interaction/README.md +++ b/packages/interaction/README.md @@ -14,4 +14,4 @@ The seams through which a human collaborates with a running agent — questions, These packages integrate through existing agent and session contracts rather than changing the loop. Interactive applications provide the concrete command, approval, and question adapters; automation uses [`acp/`](../acp/README.md), and runnable demo bundles live under [`examples/`](../examples/README.md). The product [`dsh`](../../apps/cli/README.md) CLI composes these packages directly. -The subsystem references: [approval.md](../../docs/subsystems/approval.md), [permission.md](../../docs/subsystems/permission.md), [user-interaction.md](../../docs/subsystems/user-interaction.md), [commands.md](../../docs/subsystems/commands.md), and [tui.md](../../docs/subsystems/tui.md). The automation-only ACP transport is [`acp/`](../acp/README.md), the SDK's JSON-RPC server half [`scaffold/server`](../scaffold/README.md), and the shared bin boot glue [`boot/`](../boot/README.md). +The subsystem references: [approval.md](../../docs/subsystems/approval.md), [permission.md](../../docs/subsystems/permission.md), [user-interaction.md](../../docs/subsystems/user-interaction.md), and [commands.md](../../docs/subsystems/commands.md). The automation-only ACP transport is [`acp/`](../acp/README.md), the SDK's JSON-RPC server half [`scaffold/server`](../scaffold/README.md), and the shared bin boot glue [`boot/`](../boot/README.md). diff --git a/packages/interaction/README.zh.md b/packages/interaction/README.zh.md index 6eafe8726b..170a557e40 100644 --- a/packages/interaction/README.zh.md +++ b/packages/interaction/README.zh.md @@ -14,4 +14,4 @@ 这些包通过现有的 agent(智能体)和会话契约集成,而不改变循环。交互式应用提供具体的命令、审批和提问适配器;自动化使用 [`acp/`](../acp/README.md),可运行的演示组合包位于 [`examples/`](../examples/README.md)。产品 [`dsh`](../../apps/cli/README.md) CLI(命令行界面)直接组合这些包。 -子系统参考:[approval.md](../../docs/subsystems/approval.md)、[permission.md](../../docs/subsystems/permission.md)、[user-interaction.md](../../docs/subsystems/user-interaction.md)、[commands.md](../../docs/subsystems/commands.md) 与 [tui.md](../../docs/subsystems/tui.md)。仅自动化的 ACP 传输在 [`acp/`](../acp/README.md),SDK 的 JSON-RPC 服务器一半在 [`scaffold/server`](../scaffold/README.md),共享 bin 启动胶水在 [`boot/`](../boot/README.md)。 +子系统参考:[approval.md](../../docs/subsystems/approval.md)、[permission.md](../../docs/subsystems/permission.md)、[user-interaction.md](../../docs/subsystems/user-interaction.md)与 [commands.md](../../docs/subsystems/commands.md)。仅自动化的 ACP 传输在 [`acp/`](../acp/README.md),SDK 的 JSON-RPC 服务器一半在 [`scaffold/server`](../scaffold/README.md),共享 bin 启动胶水在 [`boot/`](../boot/README.md)。 diff --git a/packages/self-modification/README.i18n.yaml b/packages/self-modification/README.i18n.yaml index 53bbf9cdbb..5a2f02826c 100644 --- a/packages/self-modification/README.i18n.yaml +++ b/packages/self-modification/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/self-modification/README.md README.md: 53f700993cce8df8729fba5a92a9498fff2412cd -README.zh.md: 90030cda33f47d128041b7619546ac1309b8465a +README.zh.md: 18e03798b90e73f1d7e61ecf7010b8c472454a08 diff --git a/packages/self-modification/README.zh.md b/packages/self-modification/README.zh.md index 90030cda33..18e03798b9 100644 --- a/packages/self-modification/README.zh.md +++ b/packages/self-modification/README.zh.md @@ -8,5 +8,3 @@ agent 修改自身运行时:检查已加载的插件与服务接口、挂载 |---|---|---| | [`tool-cordis/`](tool-cordis/README.md) | `cordis_inspect`/`cordis_mount`/`cordis_unmount` 工具:读取当前进程运行时,并在一个自有分组 fiber 下管理内存中的临时插件 | 注册到 `ctx.tools` | | [`repository-plugin/`](repository-plugin/README.md) | 通过 DSH 自有子 Plugin 准备并挂载静态 repository skills 与通用 `.mcp.json` server | 注册一个 Loader builtin | - -工具契约见 [`tool-cordis/README.md`](tool-cordis/README.md) 与 [`repository-plugin/README.md`](repository-plugin/README.md)。 diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 35fef7beaf..b053efe789 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -154,16 +154,17 @@ export const EVENT_SCOPE_PAGE: Record = { export const LINK_MAP: Readonly> = { Agent: 'core.md', AgentCancelCause: 'core.md', + AgentFactory: 'core.md', + AgentHandle: 'core.md', AgentOptions: 'core.md', AgentStatus: 'core.md', ContentBlock: 'llm-streaming.md', - ContinuationDecision: 'core.md', - ContinuationStop: 'core.md', + CreateAgentOptions: 'core.md', GenerateOptions: 'llm-streaming.md', InboxItem: 'core.md', InboxPlacement: 'core.md', MessageId: 'llm-streaming.md', - HookContext: 'core.md', + ResumeAgentOptions: 'core.md', SettleReason: 'core.md', AdapterRegistrationHandle: 'llm-streaming.md', DirectoryRegistrationHandle: 'llm-streaming.md', @@ -188,7 +189,7 @@ export const LINK_MAP: Readonly> = { PreparedReferencedMessage: 'session-reference.md', SessionReferenceCandidate: 'session-reference.md', SessionReferenceInput: 'session-reference.md', - SessionEvent: 'core.md', + SessionEvent: 'session.md', SessionId: 'core.md', SessionStartSource: 'core.md', SessionLogSnapshot: 'session-query.md', @@ -410,18 +411,15 @@ export const FOUNDATION_TYPE_NAMES: ReadonlySet = new Set([ /** Project types deliberately documented outside the subsystems catalog. */ export const TYPE_LINK_EXEMPTIONS: Readonly> = { - AgentFactory: 'agent creation seam is owned by packages/core/agent/README.md', 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', InsertReferenceRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts', ConsumeTokenRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts', InsertTextRequest: 'event-local request contract is owned by packages/client/ui-slash/src/types.ts', - AgentHandle: 'agent ownership handle is owned by packages/core/agent/README.md', BashEnvContributor: 'service-local extension 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', ManualCompactAgentContext: 'manual compaction service input is owned by packages/compact/compact/src/index.ts', - 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', CommandExecution: 'executor return contract is owned by packages/interaction/commands/src/index.ts', 'z.core.JSONSchema.BaseSchema': 'zod projection output is owned by the zod v4 API', @@ -437,7 +435,6 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { KnobState: 'projection unit state shape is owned by packages/interaction/permission/README.md', PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts', PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md', - ResumeAgentOptions: 'agent resume contract is owned by packages/core/agent/README.md', Sandbox: 'external E2B SDK handle is owned by packages/e2b/e2b/README.md', SessionForkSource: 'service-local fork input is owned by packages/core/session/src/index.ts', SubagentRunEndInfo: 'event payload contract is owned by packages/subagent/subagent/src/types.ts', diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts index 750b1935de..5d8f68f4cd 100644 --- a/scripts/project-doc-site.spec.ts +++ b/scripts/project-doc-site.spec.ts @@ -1,9 +1,9 @@ /** Tests for the documentation website projection adapter. */ import { execFileSync } from 'node:child_process' -import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' +import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' -import { join, resolve } from 'node:path' +import { basename, join, resolve } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { docsPages, type DocsPage } from '../website/docs.ts' import { @@ -269,6 +269,19 @@ describe('docsPages locale routes', () => { } }) + it('indexes every subsystem page in both sides of the folder README', () => { + const pages = globSync(join(repositoryRoot, 'docs/subsystems/*.md')) + .map(page => basename(page)) + .filter(page => !page.endsWith('.zh.md') && page !== 'README.md') + .sort() + expect(pages.length).toBeGreaterThan(0) + for (const readme of ['README.md', 'README.zh.md']) { + const rows = readFileSync(join(repositoryRoot, 'docs/subsystems', readme), 'utf8') + const missing = pages.filter(page => !rows.includes(`| [${page}](${page}) |`)) + expect(missing, `${readme} must carry one table row per subsystem page`).toEqual([]) + } + }) + it('projects translated subsystem pages while retaining explicit English fallbacks', () => { const rootPages = docsPages.filter(page => ( page.locale === 'root' && page.route.startsWith('reference/subsystems/') diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json index 3d1ca11612..137315cf39 100644 --- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json +++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json @@ -16,11 +16,11 @@ }, { "role": "user", - "content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI shape. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf. Do not copy this structure to other packages; see the [`api-remotes` README](../packages/api/remotes/README.md) for the complete boundary.\n\nThe root build follows the generated dependency order:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\nBoth tsdown passes use the same complete workspace match. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase.\n\nTypeRT runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start TypeRT. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.\n\nStatic analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already owns an explicit dependency on the TypeRT contract pass or the complete build. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate topology, the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership, and the [TypeRT Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.\n\nBusiness services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.\n\nIf a relevant local check consumes built package output, build once first:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it.\n\n### Environment variables\n\nThe real DeepSeek adapter and key-backed agent demos read credentials from the environment or from a gitignored `.env` at the repo root:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when `DEEPSEEK_API_KEY` is not set.\n\n### Git integrations\n\nThe pairing merge driver derives a conflicted `.i18n.yaml` record from the confirmed ancestor, current, and other owner blobs when both language files use Git's default text strategy and merge cleanly. It fails closed on owner conflicts, non-text merge configuration, or invalid records; after an already-stopped merge, run `pnpm run resolve-translation-pairing-conflicts`, which stages every safe pairing record and exits unsuccessfully if other pairing conflicts still need manual work. See the [bilingual documentation contract](i18n/README.md#the-pairing-contract) for the exact boundary.\n\nThe installer probes the exact Node/tsx driver entrypoint before publishing its worktree configuration. If that runtime later becomes unavailable, the Node-independent launcher writes Git's ordinary text result, leaves the sidecar unresolved, and prints the recovery path; restore dependencies and run `pnpm run resolve-translation-pairing-conflicts`, or run `git merge --abort`. If `pre-merge-commit` rejects an otherwise clean merge, Git leaves the complete result staged without a commit; repair the failure and run `git commit`, or abort. The [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract) owns the exact index and `MERGE_HEAD` states.\n\nlefthook is configured in `lefthook.yml` as a fast local checkpoint:\n\n- `pre-commit` verifies staged pairing records against the staged owner blobs, applies formatting-only ESLint fixes, validates the staged files with the project-free `.oxlintrc.staged.json` profile and applies Oxlint's native fixes, regenerates `THIRD_PARTY_NOTICES.md` when a staged file is one of its inputs, checks the staged diff for whitespace errors, and runs the vendor manifest guard.\n- `pre-merge-commit` performs the same index-backed pairing check before Git creates an automatic merge commit.\n- `pre-push` runs `pnpm run typecheck`, which completes the Host lib phase, including generated TypeRT contracts, before the Client TypeScript check.\n\nThe vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code.\n\nApart from the scoped staged-record verification, the hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.\n\nContributors can opt into the comprehensive local gate set with `pnpm run check:all`. The command is independent of the Git hooks and is not an agent instruction.\n\n### CI gates\n\nThe keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory.\n\n### Daily commands\n\nThe root [contributor instructions](../AGENTS.md#commands) summarize common commands, while [`package.json`](../package.json) and [scripts/run-gates.ts](../scripts/run-gates.ts) own the current script and gate inventories. Select the smallest checks that cover the changed surface. Documentation changes use `pnpm run doc-sync`; package-public behavior changes also update the owning README or JSDoc, and built-artifact checks require `pnpm run build` first.\n\n### Demos\n\nThe one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\nThe self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nThe ACP automation server exposes fresh agent sessions over JSON-RPC stdio and also needs `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO markers\n\nUse one of three comment tags to flag known issues in the code, ordered by urgency:\n\n- `FIXME` — an issue that should block a new release. A release should not ship with an open `FIXME` unless reviewers explicitly agree the change can be merged anyway.\n- `TODO` — an issue that should be fixed soon, once we have the resources.\n- `XXX` — an issue that we may fix someday; lowest priority, no commitment.\n\nPick the tag that matches the urgency so anyone scanning the code can tell a release blocker from a someday-maybe.\n\n### Documenting types verbatim (`ts type-equiv`)\n\nThe [core data structures](subsystems/core.md) docs paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `\"projection\": \"public-api\"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change.\n" + "content": "# Development guide\n\nEnglish | [中文](development.zh.md)\n\nThe setup tutorial takes a new contributor from prerequisites to a checked checkout. The contributor reference that follows covers repository layout, daily workflow, and CI shape. Design rationale and implementation details belong to the linked Agent Notes and scripts.\n\n## Setup tutorial\n\n### Prerequisites\n\n- Node.js supports 22.19+ and 24+. CI covers 22.19, 24, and 26; see the [Node engine floor Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md).\n- Corepack-enabled pnpm. The repo pins `pnpm@11.7.0` in `package.json`; run `corepack enable` if `pnpm --version` does not resolve through Corepack.\n- Git 2.26 or newer; hook setup enables Git's worktree-specific configuration extension.\n- Optional: a DeepSeek API key for the Web, headless, and ACP automation demos and real-API e2e tests.\n\n### First-time setup\n\nInstall dependencies from the repo root:\n\n```sh\npnpm install\n```\n\nThe install also configures worktree-local Lefthook hooks and the `dsh-translation-pairing` Git merge driver through `scripts/install-lefthook.mjs`. The [worktree-local hooks Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) owns the hook-path safety contract; the [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) owns the merge driver.\n\nIf either integration is missing because dependencies were restored from cache or `postinstall` was skipped, install them manually:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\nIf the wrapper rejects existing Git configuration or reports a stale lock, follow its diagnostic and the linked Agent Note rather than editing worktree metadata speculatively. After moving a checkout, rerun the wrapper to regenerate the owned path.\n\nRun typecheck once after a fresh clone:\n\n```sh\npnpm run typecheck\n```\n\nSetup is complete when `pnpm run typecheck` exits successfully.\n\n## Contributor reference\n\n### TypeScript project layout\n\nThe repository uses isolated Host and Client aggregates. An ordinary package is registered in exactly one aggregate: Host packages in `tsconfig.host.json` and Client packages in `tsconfig.client.json`.\n\n| File | Role | Forms a program? |\n|---|---|---|\n| `tsconfig.json` | Solution root: `extends` base, `files: []`, and references to the two aggregates. It is the tsserver discovery entry and the entry for explicitly running the complete Project Reference graph; through the inherited `paths`, it is also the resolution config for tsx running `examples/` and `scripts/`. | No |\n| `tsconfig.host.json` | Host aggregate: Host packages, examples, tests, scripts, website, and the exceptional Host project of `api/remotes`. | Yes |\n| `tsconfig.client.json` | Client aggregate: `packages/client/*` packages and their tests, `apps/web`, and the exceptional Client project of `api/remotes`. | Yes |\n| `tsconfig.base.json` | Shared compilerOptions and the source `paths` map. Also the resolution facade the vitest configs point vite-tsconfig-paths at: it has no `include`, so its `paths` apply to every importer. | No |\n| `tsconfig.base.client.json` | Browser compiler shape (`jsx`, DOM libs, `types: []`) extended by the Client aggregate and every `packages/client/*` package. | No |\n\nHost and Client stay two aggregate programs because both sides declaration-merge the cordis `Context` interface under the same keys with different services; one program seeing both merges reports a collision. The collision exists only inside a `ts.Program` — module resolution never triggers it — which is why the solution may reference both aggregates and one paths facade may span both sides. Three disciplines follow:\n\n- `tsconfig.base.json` never gains `include` or `files`: they would leak into every extending package project and narrow the facade's match-all scope.\n- A script that builds a repo-wide `ts.Program` seeds `tsconfig.host.json` or `tsconfig.client.json` explicitly — never the root solution, because flattening both aggregates into one program collides the `Context` merges.\n- A new package is registered in exactly one aggregate. Having both a Node loader entry and a browser entry is not a reason to split a package; an ordinary Client plugin produces both runtime artifacts during the Client build phase.\n\n`api/remotes` is the repository's only package with split Host and Client tsconfigs. Its Host entry must participate in the Host TypeRT graph, while its Client entry imports `/remote` declarations that Host tsdown must generate first. The package-root `tsconfig.json` is therefore only a solution, and the two aggregates and direct consumers reference `tsconfig.host.json` or `tsconfig.client.json` respectively. The workspace `constraints` gate walks the reachable Project Reference graph and checks each referencing project's own compiler face: a single-config target remains valid from either face, while a split target must name the matching leaf rather than its solution root or opposite leaf. Do not copy this structure to other packages; see the [`api-remotes` README](../packages/api/remotes/README.md) for the complete boundary.\n\nThe root build follows the generated dependency order:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\nBoth tsdown passes use the same complete workspace match. They neither scan build artifacts to discover Client packages nor maintain a Host/Client package filter list. Package-local tsdown configs select entries for the current phase through `DSH_BUILD_FACE`: an ordinary Client plugin produces both its Node loader and browser bundle during the Client phase; `api-remotes` uses `hostPhase: true` to produce its Host entry early and only its browser bundle during the Client phase. Tsdown consumes only the JavaScript emitted to `lib/types` by the preceding tsc phase.\n\nTypeRT runs only during Host tsdown, seeded by `tsconfig.host.json`. It analyzes Host types and generates both Host reflection artifacts and the Host-for-Client Remote projection; Client tsdown does not start TypeRT. Consequently, `pnpm run typecheck` runs the complete Host lib phase before Client tsc, while `pnpm run build` continues through Client tsdown and the Web build. The [API Remotes generated-contract build note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md) records this ordering decision.\n\nStatic analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Generated Host-for-Client Remote declarations are the deliberate exception: the public `typecheck`, `lint`, and `doc-typecheck` commands generate them first, while internal `*:contracts-ready` scripts assume that an invoking public command or scheduler gate already owns an explicit dependency on the TypeRT contract pass or the complete build. See the [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md) for the two-aggregate topology, the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md) for tsc-first emit ownership, and the [TypeRT Remote note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) for the gate-preparation contract.\n\nBusiness services declare callable methods on the Host with `@Remote` or `@RemoteScope`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions under `ctx.remote` and scoped `agentCtx.remote` namespaces. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order.\n\nIf a relevant local check consumes built package output, build once first:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` includes `publint`, which validates package entrypoints against the built `lib/*.js` files, and `verify-node-next-types`, which validates built declarations against a temporary NodeNext consumer. A fresh worktree has no bundled JS or declarations until `pnpm run build` runs; ordinary commits and pushes do not require that build unless their selected checks consume it.\n\n### Environment variables\n\nThe real DeepSeek adapter and key-backed agent demos read credentials from the environment or from a gitignored `.env` at the repo root:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` is optional and defaults to the public API. Never commit real credentials. The real-API e2e suites self-skip when `DEEPSEEK_API_KEY` is not set.\n\n### Git integrations\n\nThe pairing merge driver derives a conflicted `.i18n.yaml` record from the confirmed ancestor, current, and other owner blobs when both language files use Git's default text strategy and merge cleanly. It fails closed on owner conflicts, non-text merge configuration, or invalid records; after an already-stopped merge, run `pnpm run resolve-translation-pairing-conflicts`, which stages every safe pairing record and exits unsuccessfully if other pairing conflicts still need manual work. See the [bilingual documentation contract](i18n/README.md#the-pairing-contract) for the exact boundary.\n\nThe installer probes the exact Node/tsx driver entrypoint before publishing its worktree configuration. If that runtime later becomes unavailable, the Node-independent launcher writes Git's ordinary text result, leaves the sidecar unresolved, and prints the recovery path; restore dependencies and run `pnpm run resolve-translation-pairing-conflicts`, or run `git merge --abort`. If `pre-merge-commit` rejects an otherwise clean merge, Git leaves the complete result staged without a commit; repair the failure and run `git commit`, or abort. The [automatic pairing merges Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract) owns the exact index and `MERGE_HEAD` states.\n\nlefthook is configured in `lefthook.yml` as a fast local checkpoint:\n\n- `pre-commit` verifies staged pairing records against the staged owner blobs, applies formatting-only ESLint fixes, validates the staged files with the project-free `.oxlintrc.staged.json` profile and applies Oxlint's native fixes, regenerates `THIRD_PARTY_NOTICES.md` when a staged file is one of its inputs, checks the staged diff for whitespace errors, and runs the vendor manifest guard.\n- `pre-merge-commit` performs the same index-backed pairing check before Git creates an automatic merge commit.\n- `pre-push` runs `pnpm run typecheck`, which completes the Host lib phase, including generated TypeRT contracts, before the Client TypeScript check.\n\nThe vendor manifest guard checks that changes under `vendor/*/src` are staged with the matching `vendor/README.md` manifest update. See `vendor/README.md` before editing vendored code.\n\nApart from the scoped staged-record verification, the hooks intentionally do not run tests, snapshots, documentation checks, builds, or hygiene. Contributors run the [checks relevant to the changed behavior](../AGENTS.md#run-relevant-checks-locally) once; CI owns exhaustive coverage, built-artifact smokes, and the Node 22.19, 24, and 26 compatibility matrix.\n\nContributors can opt into the comprehensive local gate set with `pnpm run check:all`. The command is independent of the Git hooks and is not an agent instruction.\n\n### CI gates\n\nThe keyless [CI workflow](../.github/workflows/ci.yml) groups independent gates into broad lanes and runs a smaller compatibility signal across supported Node versions. Artifact consumers wait for one build within their lane. The separate real-API workflow runs `pnpm run test:e2e` with its configured worker bound. See [scripts/run-gates.ts](../scripts/run-gates.ts) and the workflow files for the current gate and job inventory.\n\n### Daily commands\n\nThe root [contributor instructions](../AGENTS.md#commands) summarize common commands, while [`package.json`](../package.json) and [scripts/run-gates.ts](../scripts/run-gates.ts) own the current script and gate inventories. Select the smallest checks that cover the changed surface. Documentation changes use `pnpm run doc-sync`; package-public behavior changes also update the owning README or JSDoc, and built-artifact checks require `pnpm run build` first.\n\n### Demos\n\nThe one-shot Headless coding agent needs `DEEPSEEK_API_KEY` in the environment or repo-root `.env`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\nThe self-referential cordis demo can inspect and modify its live plugin runtime and needs the same credentials (`web` by default, or `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nThe ACP automation server exposes fresh agent sessions over JSON-RPC stdio and also needs `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO markers\n\nUse one of three comment tags to flag known issues in the code, ordered by urgency:\n\n- `FIXME` — an issue that should block a new release. A release should not ship with an open `FIXME` unless reviewers explicitly agree the change can be merged anyway.\n- `TODO` — an issue that should be fixed soon, once we have the resources.\n- `XXX` — an issue that we may fix someday; lowest priority, no commitment.\n\nPick the tag that matches the urgency so anyone scanning the code can tell a release blocker from a someday-maybe.\n\n### Documenting types verbatim (`ts type-equiv`)\n\nThe [subsystems](subsystems/README.md) pages paste source-equivalent declarations together with their original JSDoc so a reader sees the exact shape and source contract. To keep a paste from drifting when source changes, fence it as ` ```ts type-equiv ` (instead of ` ```ts `) and register it in `scripts/type-equiv.manifest.json` with the source file and symbol it mirrors:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv` (part of `doc-sync`) then extracts that symbol's declaration and attached JSDoc from source via the TypeScript parser and asserts the block matches both. For a class whose implementation bodies do not belong in the catalog, use ` ```ts public-api ` and set `\"projection\": \"public-api\"`; the checked projection retains the public fields, constructor, accessors, methods, and original class/member JSDoc while omitting bodies and private or protected members. Comparison ignores whitespace and non-JSDoc comments but requires every original JSDoc comment, including member documentation, so readers see the source contract beside the exact shape. The gate enforces a 1:1 correspondence by document, symbol, and projection between primary blocks and manifest entries; a paired `.zh.md` block reuses its unsuffixed sibling's entry only when the whole tracked fence sequence is byte-identical and ordered identically. `doc-typecheck` applies the same derivative rule to compilable fences, while skipping both source-equivalence fence kinds from compilation and its opt-out ratio. When you change a documented declaration or its JSDoc, the gate fails until you update the paste; when you add or remove a primary block, update the manifest in the same change.\n" }, { "role": "assistant", - "content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 形态。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全契约;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf。不要把该结构推广到其他包;完整边界见 [`api-remotes` README](../packages/api/remotes/README.md)。\n\n根构建按生成依赖排序:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\n两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package,也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。\n\nTypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成契约构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。\n\n静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本以调用它的公共命令或调度器门禁已经显式依赖 TypeRT 契约 pass 或完整构建为前提。双 aggregate 拓扑见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md),tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md),门禁准备契约见 [TypeRT Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md)。\n\n业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。\n\n如果相关的本地检查需要使用构建后的包产物,请先构建一次:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。\n\n### 环境变量\n\n真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 文件读取凭证:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` 可选,默认为公开 API。请勿提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。\n\n### Git 集成\n\n当两种语言的文件都使用 Git 默认文本策略且能干净合并时,配对合并驱动会根据已确认的祖先、当前和另一侧的配对文档 blob,推导出发生冲突的 `.i18n.yaml` 记录。配对文档发生冲突、存在非文本合并配置或记录无效时,它会拒绝处理并保留冲突;如果合并已经因冲突而停止,请运行 `pnpm run resolve-translation-pairing-conflicts`,该命令会暂存每份可安全生成的配对记录;如果其他配对冲突仍需手工处理,则以非零状态退出。确切边界见[双语文档契约](i18n/README.md#the-pairing-contract)。\n\n安装脚本在发布 worktree 配置前,会探测确切的 Node/tsx 驱动入口点。如果该运行时之后变得不可用,不依赖 Node 的启动器会写入 Git 的普通文本合并结果、让伴随文件保持未解决状态,并打印恢复路径;请恢复依赖后运行 `pnpm run resolve-translation-pairing-conflicts`,或运行 `git merge --abort`。如果 `pre-merge-commit` 拒绝原本能干净完成的合并,Git 会把完整结果留在暂存区但不创建提交;请修复失败后运行 `git commit`,或中止合并。确切的索引与 `MERGE_HEAD` 状态由[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract)负责记录。\n\nlefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点:\n\n- `pre-commit` 对照暂存的配对文档 blob 校验暂存的配对记录,应用仅用于格式化的 ESLint 修复,使用不加载项目的 `.oxlintrc.staged.json` 配置验证暂存文件并应用 Oxlint 的原生修复,在暂存文件属于 `THIRD_PARTY_NOTICES.md` 的输入时重新生成该文件,然后检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫;\n- `pre-merge-commit` 在 Git 创建自动合并提交前执行同样以索引为准的配对检查;\n- `pre-push` 运行 `pnpm run typecheck`;该命令会先完成包含 TypeRT 契约生成的完整 Host lib 阶段,再运行 Client TypeScript 检查。\n\nvendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。\n\n除限定范围的暂存记录校验外,这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。\n\n贡献者可以选择运行 `pnpm run check:all`,执行全面的本地门禁集。该命令独立于 Git 钩子,也不是对 agent 的指令。\n\n### CI 门禁\n\nkeyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。\n\n### 日常命令\n\n根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;package 公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。\n\n### 演示\n\n单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\n自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nACP 自动化服务器通过 JSON-RPC stdio 提供全新 agent 会话,同样需要 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO 标记\n\n请使用以下三种注释标签之一标记代码中的已知问题,按紧急程度排序:\n\n- `FIXME`:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 `FIXME`;\n- `TODO`:应当尽快修复的问题,等资源到位即可处理;\n- `XXX`:也许某天会修复的问题,优先级最低,不作承诺。\n\n请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。\n\n### 逐字记录类型(`ts type-equiv`)\n\n[核心数据结构](subsystems/core.md)文档会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `\"projection\": \"public-api\"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。\n" + "content": "# 开发指南\n\n[English](development.md) | 中文\n\n搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 形态。设计依据与实现细节属于链接的 Agent Note 和脚本。\n\n## 搭建教程\n\n### 前置条件\n\n- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 Agent Note](../.agents/notes/implemented/process/2026-07-06-node-engine-floor.md)。\n- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。\n- Git 2.26 或更高版本;钩子设置会启用 Git 的 worktree 专属配置扩展。\n- 可选:一个 DeepSeek API key,用于 Web、headless 和 ACP(Agent Client Protocol)自动化 agent(智能体)演示以及真实 API 的 e2e 测试。\n\n### 首次搭建\n\n在仓库根目录安装依赖:\n\n```sh\npnpm install\n```\n\n安装过程还会通过 `scripts/install-lefthook.mjs` 配置 worktree 本地的 Lefthook 钩子和 `dsh-translation-pairing` Git 合并驱动。[worktree 本地钩子 Agent Note](../.agents/notes/implemented/process/2026-07-27-worktree-local-lefthook.md) 负责钩子路径的安全契约;[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责合并驱动。\n\n如果依赖是从缓存恢复或 `postinstall` 被跳过而导致任一集成缺失,请手动安装:\n\n```sh\nnode scripts/install-lefthook.mjs\n```\n\n如果包装脚本拒绝现有 Git 配置或报告陈旧锁,请遵循其诊断和所链接的 Agent Note,不要凭猜测编辑 worktree 元数据。移动检出目录后,请重新运行包装脚本以重新生成自有路径。\n\n新克隆后请先运行一次类型检查:\n\n```sh\npnpm run typecheck\n```\n\n`pnpm run typecheck` 成功退出即表示搭建完成。\n\n## 贡献者参考\n\n### TypeScript 项目布局\n\n仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。\n\n| 文件 | 角色 | 是否构成 program? |\n|---|---|---|\n| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |\n| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |\n| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |\n| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |\n| `tsconfig.base.client.json` | 浏览器编译形状(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |\n\nHost 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:\n\n- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。\n- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。\n- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。\n\n`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf。不要把该结构推广到其他包;完整边界见 [`api-remotes` README](../packages/api/remotes/README.md)。\n\n根构建按生成依赖排序:\n\n```sh\ntsc -b tsconfig.host.json\ntsdown --env.DSH_BUILD_FACE host\ntsc -b tsconfig.client.json\ntsdown --env.DSH_BUILD_FACE client\npnpm run build:web\n```\n\n两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package,也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。\n\nTypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成契约构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。\n\n静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本以调用它的公共命令或调度器门禁已经显式依赖 TypeRT 契约 pass 或完整构建为前提。双 aggregate 拓扑见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md),tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md),门禁准备契约见 [TypeRT Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md)。\n\n业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。\n\n如果相关的本地检查需要使用构建后的包产物,请先构建一次:\n\n```sh\npnpm run build\n```\n\n`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。\n\n### 环境变量\n\n真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 文件读取凭证:\n\n```sh\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_BASE_URL=https://... # optional\n```\n\n`DEEPSEEK_BASE_URL` 可选,默认为公开 API。请勿提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。\n\n### Git 集成\n\n当两种语言的文件都使用 Git 默认文本策略且能干净合并时,配对合并驱动会根据已确认的祖先、当前和另一侧的配对文档 blob,推导出发生冲突的 `.i18n.yaml` 记录。配对文档发生冲突、存在非文本合并配置或记录无效时,它会拒绝处理并保留冲突;如果合并已经因冲突而停止,请运行 `pnpm run resolve-translation-pairing-conflicts`,该命令会暂存每份可安全生成的配对记录;如果其他配对冲突仍需手工处理,则以非零状态退出。确切边界见[双语文档契约](i18n/README.md#the-pairing-contract)。\n\n安装脚本在发布 worktree 配置前,会探测确切的 Node/tsx 驱动入口点。如果该运行时之后变得不可用,不依赖 Node 的启动器会写入 Git 的普通文本合并结果、让伴随文件保持未解决状态,并打印恢复路径;请恢复依赖后运行 `pnpm run resolve-translation-pairing-conflicts`,或运行 `git merge --abort`。如果 `pre-merge-commit` 拒绝原本能干净完成的合并,Git 会把完整结果留在暂存区但不创建提交;请修复失败后运行 `git commit`,或中止合并。确切的索引与 `MERGE_HEAD` 状态由[自动配对合并 Agent Note](../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md#failure-contract)负责记录。\n\nlefthook 在 `lefthook.yml` 中配置,作为快速的本地检查点:\n\n- `pre-commit` 对照暂存的配对文档 blob 校验暂存的配对记录,应用仅用于格式化的 ESLint 修复,使用不加载项目的 `.oxlintrc.staged.json` 配置验证暂存文件并应用 Oxlint 的原生修复,在暂存文件属于 `THIRD_PARTY_NOTICES.md` 的输入时重新生成该文件,然后检查暂存 diff 中的空白错误,并运行 vendor manifest(元数据清单)守卫;\n- `pre-merge-commit` 在 Git 创建自动合并提交前执行同样以索引为准的配对检查;\n- `pre-push` 运行 `pnpm run typecheck`;该命令会先完成包含 TypeRT 契约生成的完整 Host lib 阶段,再运行 Client TypeScript 检查。\n\nvendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。\n\n除限定范围的暂存记录校验外,这些钩子有意不运行测试、快照、文档检查、构建或 `hygiene`。贡献者只运行一次[与改动行为相关的检查](../AGENTS.md#run-relevant-checks-locally);CI 负责全量覆盖率门禁、构建产物冒烟测试,以及 Node 22.19、24 和 26 兼容性矩阵。\n\n贡献者可以选择运行 `pnpm run check:all`,执行全面的本地门禁集。该命令独立于 Git 钩子,也不是对 agent 的指令。\n\n### CI 门禁\n\nkeyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若干宽粒度 lane,并在受支持的 Node 版本上运行一组较小的兼容性检查。产物消费方在各自 lane 内等待一次 build。单独的真实 API 工作流按其配置的 worker 上限运行 `pnpm run test:e2e`。当前门禁和 job 清单以 [scripts/run-gates.ts](../scripts/run-gates.ts) 和工作流文件为准。\n\n### 日常命令\n\n根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;package 公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。\n\n### 演示\n\n单次运行的 Headless coding agent 需要环境变量或仓库根目录 `.env` 中的 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:headless \"summarize this workspace\"\n```\n\n自指的 cordis 演示可以检查并修改其实时插件运行时,并需要相同的凭证(默认 `web`,也可用 `acp`):\n\n```sh\npnpm run demo:cordis\n```\n\nACP 自动化服务器通过 JSON-RPC stdio 提供全新 agent 会话,同样需要 `DEEPSEEK_API_KEY`:\n\n```sh\npnpm run demo:acp\n```\n\n### TODO 标记\n\n请使用以下三种注释标签之一标记代码中的已知问题,按紧急程度排序:\n\n- `FIXME`:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 `FIXME`;\n- `TODO`:应当尽快修复的问题,等资源到位即可处理;\n- `XXX`:也许某天会修复的问题,优先级最低,不作承诺。\n\n请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。\n\n### 逐字记录类型(`ts type-equiv`)\n\n[子系统](subsystems/README.md)页面会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切形状和源码契约。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号:\n\n```json\n{ \"doc\": \"docs/subsystems/session.md\", \"symbol\": \"SessionEvent\", \"source\": \"packages/core/session/src/types.ts\" }\n```\n\n`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `\"projection\": \"public-api\"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码契约和确切形状。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。\n" }, { "role": "user", diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index b3bf8eaec4..5f57b5167d 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -22,17 +22,17 @@ "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ContextForm", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ContextSnapshotSection", "source": "packages/llm/llm/src/message.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "ContextFormed", "source": "packages/llm/llm/src/message.ts" }, @@ -57,12 +57,12 @@ "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmModelDiscoveryRequest", "source": "packages/llm/llm/src/types.ts" }, { - "doc": "docs/subsystems/core.md", + "doc": "docs/subsystems/llm-streaming.md", "symbol": "LlmDiscoveredModel", "source": "packages/llm/llm/src/types.ts" }, @@ -111,11 +111,6 @@ "symbol": "LlmCallConfigAdapterDefaults", "source": "packages/llm/llm/src/call-config.ts" }, - { - "doc": "docs/subsystems/core.md", - "symbol": "SessionEvent", - "source": "packages/core/session/src/types.ts" - }, { "doc": "docs/subsystems/core.md", "symbol": "InboxTarget", @@ -1689,6 +1684,21 @@ "doc": "docs/subsystems/session-projection.md", "symbol": "ProjectionChangeListener", "source": "packages/session/session-projection/src/index.ts" + }, + { + "doc": "docs/subsystems/core.md", + "symbol": "AgentHandle", + "source": "packages/core/agent/src/index.ts" + }, + { + "doc": "docs/subsystems/core.md", + "symbol": "AgentStatus", + "source": "packages/core/agent/src/types.ts" + }, + { + "doc": "docs/subsystems/core.md", + "symbol": "AgentOptions", + "source": "packages/core/agent/src/types.ts" } ] } diff --git a/website/docs.ts b/website/docs.ts index 1f7fa316a5..3a31bf4baf 100644 --- a/website/docs.ts +++ b/website/docs.ts @@ -261,6 +261,7 @@ const subsystemsReference = pairedPages(([ ['README.md', '子系统', 'Subsystems', 0], ['core.md', '核心', 'Core', 1], ['scope.md', '作用域', 'Scopes', 2], + ['typert.md', 'TypeRT', 'TypeRT', 39], ['session.md', '会话', 'Sessions', 3], ['session-query.md', '会话查询', 'Session query', 4], ['session-reference.md', '会话引用', 'Session references', 5], @@ -293,7 +294,6 @@ const subsystemsReference = pairedPages(([ ['workspace.md', '工作区', 'Workspaces', 32], ['http-server.md', 'HTTP 服务器', 'HTTP server', 33], ['client-modules.md', '客户端模块', 'Client modules', 34], - ['tui.md', 'TUI 扩展', 'TUI extensions', 35], ['invariants.md', '运行时不变式', 'Runtime invariants', 36], ['session-projection.md', '会话投影', 'Session projections', 37], ['telemetry.md', '遥测', 'Telemetry', 38],