feat(preset): declare Agent compositions in profile YAML (#4569)

* feat(preset): declare Agent compositions in profile YAML

* fix(preset): share scope binding cleanup and refresh module graph

* fix(preset): use host syntax colors in the YAML editor

* fix(preset): write new profile declarations as block YAML

* fix(preset): align eager activation with browser and worker surfaces

* fix(ui-agent-preset): retain the existing preset card layout

* fix(preset): keep rows waiting for a Host service mounted and re-audit after settlement

A preset row whose Host provider was still activating at declaration time
was reported as waiting for a missing service, and the registry recorded the
definition as broken for the rest of the process. Split the mount audit into
failed and pending rows: failures and root-realm leaks still reject the
mount, while pending rows stay mounted and every read and binding re-audits
them after the Host Loader tree settles.

Remove the accidental self-dependency of @deepseek-ai/dsh-web-app and its
lockfile self-link. Archive the copy-only preset authoring note, which the
declarative-preset note fully supersedes, and update the ralph note's
restore recipe to the profile-patch mechanism.

* chore(lint): re-key and prune the unknown-cast baseline after the master merge

* refactor(web-app): ship each Agent preset as its own bundle patch file

`dsh.bundle.patch` accepts an ordered file list, so the four shipped preset
declarations move out of the 1000-line web patch into
`presets/<id>.patch.yml`, one insert per file, applied after the host patch.
The host patch keeps the preset registry row. Gates and test scaffolds that
read the web layer resolve every patch file through `bundlePatchPaths`
instead of one hardcoded path.

* refactor(preset): drop the save notice injected into live Agents

The notice duplicated the save result the Web editor already shows, and
after the producer-owned message sources it would have needed its own
persisted source kind. Saving now only persists and reports the activation
diagnostic; the persistence catalog is unchanged from master.

* docs: regenerate the module graph after dropping the registry's llm dependency

* test(ui-agent-preset): cover the guide dialog's keyboard handling on the declarative section

* fix(preset): address the remaining review comments

Apply the lowercase id rule only when creating a declaration, so presets
declared with other ids stay editable. Export `isBuiltInPreset` from the
display fold and key the client card groups on it. Remove the dead `error`
and `retry` locale keys, the JSDoc orphaned by the `trust` removal, the
`trust` fields in the plugin-inventory test fixtures, and the stale
`dsh-agent-presets` package name in comments and READMEs.

* refactor(preset): make the Web preset page view-and-select only

Remove the child-plugin YAML editor, the `agent_preset` tool and the
registry's read/save Remote methods. Presets are declared and changed only by
bundle patches; the settings page keeps the roster, the new-task default and
the mode help, and its question icon explains that and offers to start a
Creator-mode task. The `editing-cordis-compositions` skill and the Creator
persona describe the bundle-based path, and the authored skill-load Session
and Web snapshots are refreshed for it.

* docs(skill): make the composition skill self-sufficient for preset authoring

State where the shipped declarations live, list every declaration field,
inline a complete minimal bundle and an override example, and name the
verification calls, so Creator mode no longer reads DSH package sources to
infer the format. Refresh the authored skill-load Session for the new text.

* test(tools): drop the removed agent_preset tool from the catalog expectation
This commit is contained in:
Turtle authored and GitHub committed 2026-09-21 08:24:18 +00:00
1 parent 933d3bf39e
commit d1e22a7e24
294 files changed
+4120 -11921

No files matched your search

@@ -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-03-per-session-agent-presets.md
2026-08-03-per-session-agent-presets.md: 47f2e28c2aac583ce4ca810157e9c7abe58256d5
2026-08-03-per-session-agent-presets.zh.md: 88b643ea710364f18ff7c73a8ca0e10de0fbb44c
2026-08-03-per-session-agent-presets.md: a1f2ad294edfdaaf2dab2d02e858e4dcb0a0c746
2026-08-03-per-session-agent-presets.zh.md: b745c22c91b4017131a32796c085a869a2eabaf0
@@ -1,6 +1,7 @@
# Agent Note: A session's agent is composed from a preset cordis.yml
Status: implemented
Archived: 2026-09-18
English | [中文](2026-08-03-per-session-agent-presets.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 会话的 agent 由一份 preset cordis.yml 组装而成
Status: implemented
Archived: 2026-09-18
[English](2026-08-03-per-session-agent-presets.md) | 中文
+6
View File
@@ -160,6 +160,9 @@
"architecture/2026-08-01-packaged-ripgrep-search.i18n.yaml": "sha256:43c9128079955e974fe1da3ce1a10779b4f5a25ac3629f92e3b1de847ad45050",
"architecture/2026-08-01-packaged-ripgrep-search.md": "sha256:36cf2d6daa654abced690965338aec4628069dc77be3a1da6368a9b097a59ff4",
"architecture/2026-08-01-packaged-ripgrep-search.zh.md": "sha256:45b2fe8e0d492eeff313d2ef3c98fd69761b3257eee3476b8bf53312e8b6095f",
"architecture/2026-08-03-per-session-agent-presets.i18n.yaml": "sha256:3fe057a67e41f9ce4f5cc8b162e264494b39169588fe9c9e48dcf95fd1b208a1",
"architecture/2026-08-03-per-session-agent-presets.md": "sha256:d4c6c6af11f41b57e95ca359e2cbe09780cb0851037998824a5966cab5e12626",
"architecture/2026-08-03-per-session-agent-presets.zh.md": "sha256:c91248b2d9cc5c51c77e9d195aca5d51a06684971b1afafc60b2def72248afca",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.i18n.yaml": "sha256:c1a59c3c04afada633740c808b98e765ccad09220c4c79d8f4207313c9e9aab6",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.md": "sha256:fae0c59ece887f7d9344b6254d20cc671894074d53eefffb85dbf45b9104d2b6",
"architecture/2026-08-03-pi-ai-declared-provider-catalog.zh.md": "sha256:8032cefedd51b292d810051ff32e8825958e6d90b2e523a374294ef2f859ca1e",
@@ -1780,6 +1783,9 @@
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.i18n.yaml": "sha256:e000606d801984d4ee473779891ffe8c197f64f54b75efd82fb46dc84217cd59",
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.md": "sha256:b93ce0d79ee72306d569d80fd81308a2b5704d95df8854b7cbd2f3d898b3f776",
"simplification/2026-08-06-user-bubbles-drop-the-branch-action.zh.md": "sha256:999e3e1252f771c420506a80b7c0d512b70da6770e3e62bb92169180b4cee3e8",
"simplification/2026-08-08-copy-only-preset-authoring.i18n.yaml": "sha256:43bba45597b198a94e3cbf8df31794239c521ecf002338636dca2c099acd94e1",
"simplification/2026-08-08-copy-only-preset-authoring.md": "sha256:be77d385a3c4db534fbbd5b4a830fcb7f6bcd8401221617859569e2c4d837069",
"simplification/2026-08-08-copy-only-preset-authoring.zh.md": "sha256:4c544a08e741c4a273a89dc2a3fda44dffb46af15d0feb8f7bf8efa816bfbd13",
"simplification/2026-08-08-remove-cli-demo.i18n.yaml": "sha256:1baa8649e186e159afb03f112059f46f7a35c303a6039374db8bc4ca743d4bdf",
"simplification/2026-08-08-remove-cli-demo.md": "sha256:a34ce79b674c67a6a47d99c1fa7141b2ae58e36b99da5989d323d229d1921baa",
"simplification/2026-08-08-remove-cli-demo.zh.md": "sha256:96a2a7cc34c7b0abf71b77d666a6349d681fd8a0d9a9c81e30d2aa538d62797a",
@@ -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/archived/simplification/2026-08-08-copy-only-preset-authoring.md
2026-08-08-copy-only-preset-authoring.md: 07f8a6553cf9c70d5289a074b6e6d0d677dd4a28
2026-08-08-copy-only-preset-authoring.zh.md: 58757a9774354b116d235f890beb136034f29904
@@ -1,6 +1,7 @@
# Agent Note: Copy-only preset authoring, and the way into a preset's files
Status: implemented
Archived: 2026-09-20
English | [中文](2026-08-08-copy-only-preset-authoring.zh.md)
@@ -1,6 +1,7 @@
# Agent Note: 仅复制的 preset 创作,与通往 preset 文件的入口
Status: implemented
Archived: 2026-09-20
[English](2026-08-08-copy-only-preset-authoring.md) | 中文
@@ -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: ff00327c388b0468e5e98d488141aa35eb40d104
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 3f7f18a9907b2f43f7cd8467f054f0d0bfcef14a
2026-07-10-single-file-executable-sdk-runtime-distribution.md: 7260d11c84f8fb07cf6c4691357f92e0d00fd8f3
2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md: 653fb3d9b782d9010f693e4af8ad94475abb9d94
@@ -40,7 +40,7 @@ The Python client supplies an explicit Harness home and selects the `sdk` profil
Inside the exe's VFS sits a **real package tree in build-artifact form** (each package's `lib/` plus a real `node_modules`). The packaged JSON-RPC entry supplies its installed harness base to app-boot's root Include: relative plugin specifiers resolve from the external configuration directory, while bare package names resolve from the VFS, so a configuration inside another Node project cannot shadow the packaged plugin set. The ordinary development bin leaves bare packages configuration-owned. Bare specifiers in the packaged entry resolve upward along `node_modules` from the entry's position inside the VFS and land inside the VFS naturally. The closed set needs no allowlist code — the set is whatever the VFS has installed, and importing a name outside the set fails.
The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-python-runtime-closure`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) reads every shipped `packages/preset/agent-presets/presets/*/agent.cordis.yml`, evaluates `disabled` conditions that compare `process.platform` for every target in `python/sdk-runtime/platforms.json`, and requires each active workspace plugin at the runtime root through an explicit `workspace:` dependency. It also traverses every workspace package covered by that manifest and requires every non-optional workspace peer, reporting the complete preset or referencing-package → missing-dependency chain; unknown platform conditions remain active so a plugin cannot be omitted by an unsupported expression. `pnpm run hygiene`, CI static, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`.
The deploy root is [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json) (`dsh-python-runtime-closure`, a pnpm workspace member and a zero-code pure dependency manifest) — the unified source of truth for "which plugins the exe ships" and "what the Python runtime distributes". Adding a plugin to the exe = adding one dependency line to the manifest and repackaging. [`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) reads every shipped `packages/bundle/web-app/cordis.patch.yml*/agent.cordis.yml`, evaluates `disabled` conditions that compare `process.platform` for every target in `python/sdk-runtime/platforms.json`, and requires each active workspace plugin at the runtime root through an explicit `workspace:` dependency. It also traverses every workspace package covered by that manifest and requires every non-optional workspace peer, reporting the complete preset or referencing-package → missing-dependency chain; unknown platform conditions remain active so a plugin cannot be omitted by an unsupported expression. `pnpm run hygiene`, CI static, and the single-exe build run it before packaging. Deploy also packs by each package's `files`, so the shared chunks tsdown splits out must be covered by `files`.
The deploy root includes `@deepseek-ai/dsh-mcp-client` as an explicitly supported custom-configuration plugin even though no shipped preset mounts it. An external config can therefore connect to user-supplied stdio and Streamable HTTP MCP servers and register their tools; the distribution does not carry those servers. The optional resource service provides MCP Resources; MCP Prompts remain unsupported. The executable and installed-wheel smokes start a temporary stdio server, discover its tool, and complete one model-requested call.
@@ -40,7 +40,7 @@ Python 客户端提供显式 Harness home,并选择 `sdk` profile 与有序 pa
exe 的 VFS 内是**构建产物形态的真实包树**(各包的 `lib/` + 真实 `node_modules`)。打包专用 JSON-RPC 入口会向 app-boot 的根 Include 提供自身已安装 harness 的基准位置:相对插件说明符从外部配置目录解析,裸包名则从 VFS 解析,因此位于另一个 Node 项目内的配置无法遮蔽已打包的插件集合。普通开发 bin 仍由配置项目提供裸包。打包入口中的裸包名从该入口在 VFS 内的位置沿 `node_modules` 向上解析,自然落在 VFS 内。封闭集不需要白名单代码——VFS 中安装了什么,集合中就有什么;`import()` 集合外的名称会失败。
部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-python-runtime-closure`,pnpm 工作区成员、零代码纯依赖 manifest),也是「exe 安装哪些插件」与「Python 运行时分发什么」的统一真源。向 exe 添加插件,就是在 manifest 中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 读取每个已发布的 `packages/preset/agent-presets/presets/*/agent.cordis.yml`,针对 `python/sdk-runtime/platforms.json` 中的每个目标解析比较 `process.platform` 的 `disabled` 条件,并要求该目标启用的每个工作区插件都通过显式的 `workspace:` 依赖列在运行时根目录。它还遍历该 manifest 覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列出,并报告“preset 或引用包 → 缺失依赖”的完整链路;无法识别的平台条件会保持启用,避免因不支持的表达式遗漏插件。`pnpm run hygiene`、CI 静态检查与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。
部署根目录是 [`python/sdk-runtime/package.json`](../../../../python/sdk-runtime/package.json)(`dsh-python-runtime-closure`,pnpm 工作区成员、零代码纯依赖 manifest),也是「exe 安装哪些插件」与「Python 运行时分发什么」的统一真源。向 exe 添加插件,就是在 manifest 中增加一行依赖后重新打包。[`scripts/verify-runtime-closure.ts`](../../../../scripts/verify-runtime-closure.ts) 读取每个已发布的 `packages/bundle/web-app/cordis.patch.yml*/agent.cordis.yml`,针对 `python/sdk-runtime/platforms.json` 中的每个目标解析比较 `process.platform` 的 `disabled` 条件,并要求该目标启用的每个工作区插件都通过显式的 `workspace:` 依赖列在运行时根目录。它还遍历该 manifest 覆盖的全部工作区包,要求每个非可选的工作区对等依赖(peer dependency)都显式列出,并报告“preset 或引用包 → 缺失依赖”的完整链路;无法识别的平台条件会保持启用,避免因不支持的表达式遗漏插件。`pnpm run hygiene`、CI 静态检查与 single-exe 构建都会在打包前运行该门禁。部署还会依据各包的 `files` 字段打包,因此 tsdown 拆出的共享分片必须被 `files` 覆盖。
部署根目录显式包含 `@deepseek-ai/dsh-mcp-client`,将其作为自定义配置可用的插件,即使随附 preset 均未挂载该插件。外部配置因此可以连接由用户提供的 stdio 与 Streamable HTTP MCP server 并注册其工具;分发物不包含这些 server。可选资源服务提供 MCP Resources;MCP Prompts 仍不受支持。可执行程序与已安装 wheel 包的冒烟测试会启动临时 stdio server,发现其工具,并完成一次由模型请求的调用。
@@ -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-10-host-plane-ownership-after-presets.md
2026-08-10-host-plane-ownership-after-presets.md: 78410368cdc66b14fe478a4de9f0ae7a1cbefac5
2026-08-10-host-plane-ownership-after-presets.zh.md: 862b793fab23b209f6f43b2e8045ad7330c30ba0
2026-08-10-host-plane-ownership-after-presets.md: daf48f27c2228b71319d6f5410e6cabe3bc02b39
2026-08-10-host-plane-ownership-after-presets.zh.md: 56babf5e715584b4ee061a334158d46e59b0d350
@@ -6,7 +6,7 @@ English | [中文](2026-08-10-host-plane-ownership-after-presets.zh.md)
## Problem
[Per-session agent presets](2026-08-03-per-session-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([child agents join their parent's preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)).
[Per-session agent presets](2026-09-18-declarative-agent-presets.md) moved every model-facing row onto the agent plane, and each later fix has been one reader that assumed the world before the move. `tasks` came back to the host because a preset row outside its realm resolved it; `goals` never left for the same reason; a child agent's `toolFilter` was repaired once every model-facing tool became an ancestor contribution rather than a global one ([child agents join their parent's preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.md)).
Two more readers were still on the wrong side of that line.
@@ -18,15 +18,15 @@ Nothing named an agent that joined no preset. The join is a scope-parent link; w
**The meter is host-plane.** `dsh-token-meter` returns to the host composition and leaves the presets' `isolate` map, so `compaction-basic` and `tool-result-pruner` resolve the one host instance from inside their realm. The presets keep the realm and the backend — what a preset chooses is whether its agent compacts, not whether its tokens are counted. This is the criterion `tasks` and `goals` are already read by, applied to a Service whose *projection* reach is what made preset ownership wrong: a unit whose empty value is indistinguishable from a real one cannot be per-composition while the table it registers into is per-process.
**An unjoined agent is named twice, at two different points.** `AgentPresets` logs one warning per agent published with a scope chain of length one while a roster is configured. The invariant companion fails instead — and at `system-prompt/assemble`, not at publication, because an unjoined agent is legal until it addresses a model: `recompose` binds exactly such an agent as its first link, and prompt assembly is the only caller that supplies an agent scope, so a host assembly and a standing mount are both correctly out of range.
**An unjoined agent fails at model use.** The invariant companion checks `system-prompt/assemble`, because a bare Agent can legally be bound later through `recompose`. Host-only and cold-scope prompt reads have no Agent and remain outside this check.
Three limits stay open and are recorded where they bite rather than fixed here: projection key presence is not a per-session capability signal ([`dsh-session-projection`](../../../../packages/session/session-projection/README.md)); a superseded standing generation is never reclaimed, which the settings-page authoring flow turns into a per-save cost ([`dsh-agent-presets`](../../../../packages/preset/agent-presets/README.md)); and a temporary plugin mounted through `cordis_mount` belongs to the composition rather than the session that mounted it ([`dsh-tool-cordis`](../../../../packages/extensions/tool-cordis/README.md)).
Projection key presence is not a per-session capability signal ([session projection](../../../../packages/session/session-projection/README.md)). Temporary plugins mounted through `cordis_mount` belong to the composition rather than the invoking session ([tool Cordis](../../../../packages/extensions/tool-cordis/README.md)). [Declarative presets](2026-09-18-declarative-agent-presets.md) own revision retention and reclaim retired trees after their final reference releases.
## Testing
`apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any preset in the file mounts — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units.
`apps/cli/tests/web-agent-presets.e2e.ts` reads `ctx.get('tokenMeter')` on the booted Web composition before any Agent is created — a preset-side meter sits behind an `isolate` realm and is invisible to `ctx.get`, so the read is an ownership assertion rather than a mount-order coincidence — then asserts a `minimal` session's snapshot carries all three units.
`packages/preset/agent-presets/tests/mount.spec.ts` asserts the warning fires exactly once for a bare agent and not at all for a joined one. `tests/invariant.spec.ts` carries the negative control: an unjoined agent's assembly rejects, while a joined agent's assembly and a scopeless host assembly both pass.
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` rejects an unjoined Agent's assembly and accepts a joined Agent, a Host read and a cold-scope read.
## Alternatives considered
@@ -42,4 +42,4 @@ Three limits stay open and are recorded where they bite rather than fixed here:
The context meter becomes a per-session fact instead of a function of mount history. A preset can no longer opt out of token accounting; no shipped preset did, and `minimal` now says it drops auto-compaction rather than the accounting.
The warning is advisory, so a deployment that adds a roster to the ACP or SDK-server entry points still starts agents with no tools — it just says so once per agent instead of silently. The invariant reaches only compositions that load `dsh-invariants`, which fences package tests and development hosts, not a shipped one.
The invariant reaches compositions loading `dsh-invariants`. Production entry points must compose Agents explicitly; installing the registry alone does not bind them.
@@ -6,7 +6,7 @@ Status: implemented
## 问题
[逐会话 agent preset](2026-08-03-per-session-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([子 agent 加入父方 preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md))。
[逐会话 agent preset](2026-09-18-declarative-agent-presets.zh.md) 把每一个面向模型的行搬上了 agent 平面,此后的每一处修复都是一个仍按搬迁之前的世界写成的读取点。`tasks` 因为 realm 之外的 preset 行要解析它而搬回宿主;`goals` 因为同样的理由从未离开;而当所有面向模型的工具都变成祖先贡献之后,子 agent 的 `toolFilter` 也已被修好([子 agent 加入父方 preset](../bug-fix/2026-08-10-child-agents-join-their-parent-preset.zh.md))。
还有两个读取点仍站在这条线的错误一侧。
@@ -18,15 +18,15 @@ Status: implemented
**meter 属于宿主平面。** `dsh-token-meter` 回到宿主组装,并离开各 preset 的 `isolate` 映射,于是 `compaction-basic` 与 `tool-result-pruner` 在自己的 realm 内部解析到那一份宿主实例。preset 保留 realm 与压缩后端——preset 选择的是它的 agent 是否压缩,而不是它的 token 是否被计。这正是 `tasks` 与 `goals` 已经采用的判据,只是这次适用于一个因**投影**触达面而不该归 preset 所有的 Service:当一个单元的空值与真实值无法区分时,只要它注册进的那张表是进程级的,它就不能是逐组装的。
**未加入的 agent 在两个不同的点上被指出两次。** 在配置了名单的前提下,`AgentPresets` 对每个作用域链长度为一就发布的 agent 记录一条警告。invariant 配套则直接失败——并且发生在 `system-prompt/assemble` 而非发布时,因为一个未加入的 agent 在它对模型说话之前都是合法的:`recompose` 绑定的正是这样一个 agent 作为它的首次链接;而提示词组装是唯一会提供 agent 作用域的调用方,因此宿主组装与常驻挂载都正确地落在检查范围之外。
**未加入的 Agent 在使用模型时失败。** 不变量 companion 检查 `system-prompt/assemble`,因为未绑定的 Agent 可以合法地稍后通过 `recompose` 绑定。纯 Host 和历史 scope 提示词读取没有 Agent,不属于此检查范围。
有三处限制不在此处修复,而是记录在会咬到它们的地方:投影 key 是否存在不能当作逐会话的能力信号([`dsh-session-projection`](../../../../packages/session/session-projection/README.zh.md));被替代的常驻代际永不回收,而设置页的编写流程把它变成每次保存的代价([`dsh-agent-presets`](../../../../packages/preset/agent-presets/README.zh.md));通过 `cordis_mount` 挂上的临时插件属于组装而非挂载它的会话([`dsh-tool-cordis`](../../../../packages/extensions/tool-cordis/README.zh.md))。
投影 key 是否存在不能代表逐会话能力([会话投影](../../../../packages/session/session-projection/README.zh.md))。通过 `cordis_mount` 挂载的临时插件属于组装,而非调用会话([Cordis 工具](../../../../packages/extensions/tool-cordis/README.zh.md))。[声明式 preset](2026-09-18-declarative-agent-presets.zh.md) 负责代际保留,在最后一个引用释放后回收退役树。
## 测试
`apps/cli/tests/web-agent-presets.e2e.ts` 在本文件中任何 preset 挂载**之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。
`apps/cli/tests/web-agent-presets.e2e.ts` 在创建任何 Agent **之前**,于已启动的 Web 组装上读取 `ctx.get('tokenMeter')`——preset 侧的 meter 会待在 `isolate` realm 里,对 `ctx.get` 不可见,因此这次读取是一次所有权断言而不是挂载顺序的巧合——随后断言一个 `minimal` 会话的快照带齐三个单元。
`packages/preset/agent-presets/tests/mount.spec.ts` 断言警告对裸 agent 恰好触发一次、对已加入的 agent 完全不触发。`tests/invariant.spec.ts` 承担负控:未加入 agent 的组装被拒绝,而已加入 agent 的组装与不带作用域的宿主组装都通过。
`packages/preset/agent-preset-registry/tests/invariant.spec.ts` 拒绝未加入的 Agent 组装,并接受已加入的 Agent、Host 读取和历史 scope 读取。
## 考虑过的替代方案
@@ -42,4 +42,4 @@ Status: implemented
context meter 成为逐会话的事实,而不再是挂载历史的函数。代价是 preset 不能再选择不做 token 记账;随附的 preset 没有一个这么做,`minimal` 现在也写明它放弃的是自动压缩而非记账。
那条警告是建议性的,因此给 ACP 或 SDK server 入口加上名单的部署依然会启动没有工具的 agent——只是每个 agent 会说一次,而不再静默。invariant 只触达装载了 `dsh-invariants` 的组装,因此它把关的是包测试与开发宿主,不是随附宿主。
不变量覆盖加载了 `dsh-invariants` 的组装。生产入口必须显式组装 Agent;仅安装注册表不会自动绑定。
@@ -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-09-09-consumer-owned-startup-strictness.md
2026-09-09-consumer-owned-startup-strictness.md: 2f16078c7051b4038c1d48120b500f0fcd465aef
2026-09-09-consumer-owned-startup-strictness.zh.md: c0fe61e5f708f79f6a5b903f3157ee358c3086bc
2026-09-09-consumer-owned-startup-strictness.md: bbe975e13b251e2e0d18c4e002efbf1d362548f3
2026-09-09-consumer-owned-startup-strictness.zh.md: 4bd2bfe026a13ccbd46215d2c0165b5a8c5327f7
@@ -18,7 +18,7 @@ The audit treats a throwing `disabled` expression as an entry failure, not a dis
The audit runs only during initial application boot. Later config HMR remains best effort and keeps the failed candidate visible for repair.
This policy governs [Web host boot](2026-07-24-web-config-tree-boot-and-transport-layering.md), including its [client plugin roster](2026-07-23-client-plugin-loading-model.md). [Per-session presets](2026-08-03-per-session-agent-presets.md) own a separate strict subtree audit.
This policy governs [Web host boot](2026-07-24-web-config-tree-boot-and-transport-layering.md), including its [client plugin roster](2026-07-23-client-plugin-loading-model.md). [Per-session presets](2026-09-18-declarative-agent-presets.md) own a separate strict subtree audit.
## Alternatives considered
@@ -18,7 +18,7 @@ Required id 为 `agent-loop`、`webserver`、`modules`、`connection`、`headles
该审计只在应用首次启动时运行。之后的 config HMR 仍采用 best effort,并保留 failed candidate 供后续修复。
该策略适用于 [Web host 启动](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md),包括其 [client 插件名册](2026-07-23-client-plugin-loading-model.zh.md)。[按会话的 preset](2026-08-03-per-session-agent-presets.zh.md)持有独立的严格子树审计。
该策略适用于 [Web host 启动](2026-07-24-web-config-tree-boot-and-transport-layering.zh.md),包括其 [client 插件名册](2026-07-23-client-plugin-loading-model.zh.md)。[按会话的 preset](2026-09-18-declarative-agent-presets.zh.md)持有独立的严格子树审计。
## 考虑过的替代方案
@@ -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/simplification/2026-08-08-copy-only-preset-authoring.md
2026-08-08-copy-only-preset-authoring.md: 13d0bfc7c49d4b97b6c444fe1ff6c6455fef53ec
2026-08-08-copy-only-preset-authoring.zh.md: 2b93ac1b81ddb2461308baffb3b6563fe240f9d5
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-18-declarative-agent-presets.md
2026-09-18-declarative-agent-presets.md: 55ec173b33b7ddd62c88c68afc8b7546074aae0a
2026-09-18-declarative-agent-presets.zh.md: 60c81de4d981818f805b1014c2e3f05f16d1e8a9
@@ -0,0 +1,39 @@
# Agent Note: Declarative Agent presets and retained revisions
Status: implemented
English | [中文](2026-09-18-declarative-agent-presets.zh.md)
## Problem
Preset directories duplicate Cordis configuration ownership. Separate discovery, metadata, copying and editing APIs cannot express the same composition through an ordinary profile patch. Disposing a definition during a save must also preserve plugins still used by running Agents.
## Decision
`dsh-agent-preset-registry` owns selection and runtime revisions; `dsh-agent-preset` declares an identity, display metadata and child plugin list in ordinary Cordis YAML. Definitions activate eagerly. Activation failures remain in the roster and reject new bindings without preventing the application from starting.
The registry owns each revision's scope and Loader tree. Agents link their own scopes to the selected revision, and children inherit their parent's exact revision. Updates retire the former revision; Agents and temporary cold readers retain references until disposal. The final release disposes a retired tree. Host registries and the Agent loop remain shared; preset service providers and consumers require an isolated realm.
The Web editor and `agent_preset` tool accept only child plugin YAML. Saving writes the active profile's user patch, preserving display metadata and unrelated configuration. Revision checks and profile locking prevent overwriting concurrent saves; validation rejects a higher-priority override that would defeat the edit. Persistence and activation outcomes are reported separately. Tool writes require approval or full access because configuration executes in the Host.
Session data continues to record the preset identity. Restart resolves that identity against current configuration and rejects a missing definition. Old executable revisions are process-local, not serialized. The existing chooser policy and blank-session selection remain: hiding the chooser uses the deployment default without deleting the saved user preference.
## Alternatives considered
**Keep directory presets alongside declarations.** Two writable sources would compete for an identity and require precedence, migration and editing rules. Ordinary profile configuration supplies the required persistence and layering, so presets have no separate paths.
**Let each declaring plugin own its live child tree.** Loader disposal during an edit would revoke tools from running Agents. Registry ownership gives retained revisions an explicit lifetime independent of the declaration.
**Load only on first selection.** Lazy activation delays diagnostics and adds pending-first-use state. Eager activation is acceptable for these compositions and exposes failures before selection.
**Reject application startup for a broken preset.** A failed optional capability set should remain repairable in Web. Healthy definitions and Host services remain available.
## Consequences
One process can run Agents with different capabilities while sharing each selected revision. Retired revisions remain in memory while referenced. Presets do not provide a security sandbox, and a user override replaces the entire child list rather than merging future builtin changes.
The former directory and per-session-mount decision is preserved as [historical context](../../archived/architecture/2026-08-03-per-session-agent-presets.md). Host service ownership remains documented in [the Host-plane note](2026-08-10-host-plane-ownership-after-presets.md).
## Testing
Registry tests cover retained parent and child scopes, cold-reader leases, selection logging and failed activation. Editor tests cross real profile patches, check conflict rejection and preserve nested expressions. The shipped Web composition test checks activation, scoped tools, Creator skills and profile edits against built packages.
@@ -0,0 +1,39 @@
# Agent Note: 声明式 Agent preset 与保留代际
Status: implemented
[English](2026-09-18-declarative-agent-presets.md) | 中文
## Problem
Preset 目录重复承担 Cordis 配置的所有权。独立的发现、元数据、复制和编辑 API 无法通过普通 profile 补丁表达相同组装。保存时释放定义,也必须保留运行中 Agent 仍在使用的插件。
## Decision
`dsh-agent-preset-registry` 负责选择与运行时代际;`dsh-agent-preset` 在普通 Cordis YAML 中声明标识、显示元数据和子插件列表。定义提前激活。激活失败保留在列表中,并拒绝新绑定,但不阻止应用启动。
注册表拥有各代际的 scope 与 Loader 树。Agent 将自身 scope 链接到选定代际,子 Agent 继承父方的确切代际。更新使旧代际退役;Agent 和临时历史读取者保留引用,直至释放。最后一个引用释放后,退役树被销毁。Host 注册表和 Agent loop 保持共享;preset 的服务提供方与消费者需要处于隔离 realm 中。
Web 编辑器与 `agent_preset` 工具只接受子插件 YAML。保存写入当前 profile 的用户补丁,保留显示元数据和无关配置。版本检查和 profile 锁防止覆盖并发保存;验证拒绝会使编辑失效的更高优先级覆盖。持久化与激活结果分别报告。配置在 Host 中执行,因此工具写入需要批准或完全访问权限。
会话数据继续记录 preset 标识。重启时根据当前配置解析该标识,定义缺失则拒绝恢复。旧的可执行代际仅保留在进程内,不会序列化。现有选择器策略和空白会话选择保持不变:隐藏选择器使用部署默认值,但不删除保存的用户偏好。
## Alternatives considered
**同时保留目录 preset 与声明。** 两个可写来源会争用标识,需要额外的优先级、迁移和编辑规则。普通 profile 配置已提供所需持久化和分层,因此 preset 不再有独立路径。
**让声明插件拥有运行中的子树。** 编辑时 Loader 释放插件会撤销运行中 Agent 的工具。注册表所有权使保留代际具有独立于声明的明确生命周期。
**首次选择时才加载。** 延迟激活推迟诊断,并增加首次使用的等待状态。提前激活适用于这些组装,且可在选择前暴露失败。
**Preset 损坏就拒绝应用启动。** 可选能力集失败后应仍能在 Web 中修复。健康定义和 Host 服务保持可用。
## Consequences
同一进程可以运行能力不同的 Agent,同时共享各选定代际。退役代际在仍有引用时占用内存。Preset 不提供安全沙箱,用户覆盖会替换整个子插件列表,不会合并后续内置变更。
原有目录与逐会话挂载决策保留为[历史背景](../../archived/architecture/2026-08-03-per-session-agent-presets.md)。Host 服务所有权继续由 [Host 平面说明](2026-08-10-host-plane-ownership-after-presets.zh.md)记录。
## Testing
注册表测试覆盖父子 scope 保留、历史读取租约、选择日志及激活失败。编辑器测试使用真实 profile 补丁,检查冲突拒绝并保留嵌套表达式。随附 Web 组装测试通过构建产物验证激活、作用域工具、Creator 技能和 profile 编辑。
@@ -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-10-child-agents-join-their-parent-preset.md
2026-08-10-child-agents-join-their-parent-preset.md: 5321d99bad35cf26ebc0e7842dc68c2775bc384b
2026-08-10-child-agents-join-their-parent-preset.zh.md: f0d5b59c3787ef69c2c983da0c8ec0ff4a43e6e7
2026-08-10-child-agents-join-their-parent-preset.md: 7de247a7a3f75f467ebf5c8387eb8fb8e2f7e009
2026-08-10-child-agents-join-their-parent-preset.zh.md: c9d392cd75ee9c06005aa53f4f68a8153fc7633b
@@ -6,7 +6,7 @@ English | [中文](2026-08-10-child-agents-join-their-parent-preset.zh.md)
## Problem
Tool and prompt-section visibility is inherited along `dsh-scope`'s parent chain, and an agent's scope key is minted with no parent. [Per-session agent presets](../architecture/2026-08-03-per-session-agent-presets.md) moved every model-facing row onto the agent plane and made `AgentPresets.mount()` the one thing that binds that parent link — from the api-proxy's session create, resume, and fork paths. The two in-process subagent drivers compose their children through `applyChildComposition()`, which installed only the per-child persona and tool filter, so a child's scope chain had length one and its registry view resolved the global layer alone.
Tool and prompt-section visibility is inherited along `dsh-scope`'s parent chain, and an agent's scope key is minted with no parent. [Per-session agent presets](../architecture/2026-09-18-declarative-agent-presets.md) moved every model-facing row onto the agent plane and made `AgentPresets.mount()` the one thing that binds that parent link — from the api-proxy's session create, resume, and fork paths. The two in-process subagent drivers compose their children through `applyChildComposition()`, which installed only the per-child persona and tool filter, so a child's scope chain had length one and its registry view resolved the global layer alone.
That layer is now empty in any deployment with a preset roster: the web-app patch layer disables every host-plane tool row. A one-shot child therefore reached the model with zero tools, a continuable child with only the host-plane `report`, and neither carried its parent's persona, workspace context, plan-mode section, or skill catalog. The fork path had already been given the same treatment for the same reason; delegation had not.
@@ -40,7 +40,7 @@ Giving the child its parent's tools exposed a second defect the same agent-plane
## Testing
`packages/preset/agent-presets/tests/mount.spec.ts` covers the join against real fixture compositions: the child sees its parent's tools and prompt sections, no second generation is mounted, the join survives the parent's disposal (a background child outliving its parent), the reported id matches, a parent without a preset joins nothing, and an unscoped context is refused.
`packages/preset/agent-preset-registry/tests/mount.spec.ts` covers the join against real fixture compositions: the child sees its parent's tools and prompt sections, no second generation is mounted, the join survives the parent's disposal (a background child outliving its parent), the reported id matches, a parent without a preset joins nothing, and an unscoped context is refused.
`packages/core/tools/tests/scoped.spec.ts` covers the restriction rule directly: a child's filter removes a tool it inherited from an ancestor scope, the child's own registrations survive its own filter, and an ancestor's restriction still reaches every scope nested inside it.
@@ -6,7 +6,7 @@ Status: implemented
## 问题
工具与提示段的可见性沿 `dsh-scope` 的父链继承,而 agent 的 scope key 铸造出来时没有父。[逐会话 agent preset](../architecture/2026-08-03-per-session-agent-presets.zh.md) 把所有面向模型的行搬到了 agent 平面,并让 `AgentPresets.mount()` 成为绑定那条父链的唯一途径——调用点在 api-proxy 的会话创建、恢复与 fork 路径上。两个进程内 subagent 驱动通过 `applyChildComposition()` 组装子 agent,而它只安装了逐子 agent 的 persona 与工具限制,于是子 agent 的 scope 链长度为一,其注册表视图只能解析到全局层。
工具与提示段的可见性沿 `dsh-scope` 的父链继承,而 agent 的 scope key 铸造出来时没有父。[逐会话 agent preset](../architecture/2026-09-18-declarative-agent-presets.zh.md) 把所有面向模型的行搬到了 agent 平面,并让 `AgentPresets.mount()` 成为绑定那条父链的唯一途径——调用点在 api-proxy 的会话创建、恢复与 fork 路径上。两个进程内 subagent 驱动通过 `applyChildComposition()` 组装子 agent,而它只安装了逐子 agent 的 persona 与工具限制,于是子 agent 的 scope 链长度为一,其注册表视图只能解析到全局层。
在任何配置了 preset roster 的部署里,那一层现在是空的:web-app 补丁层禁用了全部宿主平面工具行。因此一次性子 agent 抵达模型时工具为零,可继续子 agent 只剩宿主平面的 `report`,两者都不带父方的 persona、工作区上下文、plan-mode 段与技能目录。fork 路径此前已因同一理由做过相同处理;委派没有。
@@ -40,7 +40,7 @@ Status: implemented
## 测试
`packages/preset/agent-presets/tests/mount.spec.ts` 用真实 fixture 组装覆盖该加入:子 agent 看到父方的工具与提示段、不会挂载出第二个代际、加入在父方 dispose 后依然成立(活得比父方久的后台子 agent)、上报的 id 一致、没有 preset 的父方不产生加入、以及无 scope 的上下文被拒绝。
`packages/preset/agent-preset-registry/tests/mount.spec.ts` 用真实 fixture 组装覆盖该加入:子 agent 看到父方的工具与提示段、不会挂载出第二个代际、加入在父方 dispose 后依然成立(活得比父方久的后台子 agent)、上报的 id 一致、没有 preset 的父方不产生加入、以及无 scope 的上下文被拒绝。
`packages/core/tools/tests/scoped.spec.ts` 直接覆盖该限制规则:子 agent 的过滤器能移除它从祖先作用域继承来的工具、子 agent 自身的注册在自己的过滤器下存活、祖先的限制仍作用于其内嵌套的每个作用域。
@@ -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-09-12-ralph-off-in-shipped-defaults.md
2026-09-12-ralph-off-in-shipped-defaults.md: 4ae434a0e9d6283c2934cdf74a562df360daa12e
2026-09-12-ralph-off-in-shipped-defaults.zh.md: 490b33d85be411826d03c5def08b1905062ff830
2026-09-12-ralph-off-in-shipped-defaults.md: fbd7ea72e02302f45029d2b7677d9a4d15ee2e63
2026-09-12-ralph-off-in-shipped-defaults.zh.md: 377f8656236b55dc8de79a78abe742b595e3617c
@@ -16,7 +16,7 @@ That tool shipped enabled in `packages/bundle/base/cordis.patch.yml` and in thre
The `ptc` preset also declares `workflow-ptc` disabled. That preset kept the engine for `ralph` alone after it dropped the general `workflow` tool, so disabling `ralph` left the engine with no consumer in that composition.
Each disabled row carries its restore recipe in a local comment. An overlay row re-enables the capability for a base-backed profile from `$DSH_HOME/cordis.patch.yml` or a `--patch` file. Preset files take no patches (`packages/preset/agent-presets/README.md`), so a Web session that wants `ralph` duplicates the preset under a **new id** into `$DSH_HOME/.agent-presets` and drops `disabled`; a copy reusing the shipped id is shadowed by the shipped root, which wins duplicate ids (`packages/preset/agent-presets/src/index.ts`), and `copy()` refuses an id any root already supplies. In `ptc` the duplicate drops `disabled` from the tool row and the engine row together, because `tool-ralph` injects `ctx.workflowEngine`.
Each disabled row carries its restore recipe in a local comment. An overlay row re-enables the capability for a base-backed profile from `$DSH_HOME/cordis.patch.yml` or a `--patch` file. Shipped presets are `@deepseek-ai/dsh-agent-preset` rows in the Web bundle patch (`packages/bundle/web-app/cordis.patch.yml`), so a Web session that wants `ralph` overrides that row's `config.plugins` from the profile patch, or saves a preset under a **new id** through the Web editor, which persists the declaration in `$DSH_HOME/profiles/<profile>/cordis.patch.yml`; the registry refuses a duplicate preset id (`packages/preset/agent-preset-registry/src/index.ts`). In `ptc` the override drops `disabled` from the tool row and the engine row together, because `tool-ralph` injects `ctx.workflowEngine`.
`packages/bundle/web-app/cordis.patch.yml` restates that disable for its own layer. `validatePresetPlaneSeparation` in `scripts/verify-cordis-config.ts` collects declared row ids without regard to `disabled`, so deleting that row would return `tool-ralph` to the Web host plane and collide with the same id in every preset.
@@ -44,6 +44,6 @@ A default Web, headless, sdk, acp, or custom base-backed session no longer offer
## Verification
`packages/preset/agent-presets/tests/shipped-root.spec.ts` pins that every preset carrying `tool-ralph` disables it, that the `minimal` roster carries no such row, that `ptc` disables `workflow-ptc`, and that `standard` and `cordis` keep the engine enabled for their `workflow` tool. `apps/cli/tests/web-agent-presets.e2e.ts` and `apps/web/tests/shipped-composition.e2e.ts` pin the exact default and PTC tool catalogs, so a row that stops contributing is a test failure rather than a silently shorter list. `scripts/verify-cordis-config.ts` continues to pass its plane-separation check against the disabled rows.
`apps/cli/tests/web-agent-presets.e2e.ts` pins that every preset carrying `tool-ralph` disables it, that the `minimal` roster carries no such row, that `ptc` disables `workflow-ptc`, and that `standard` and `cordis` keep the engine enabled for their `workflow` tool. `apps/cli/tests/web-agent-presets.e2e.ts` and `apps/web/tests/shipped-composition.e2e.ts` pin the exact default and PTC tool catalogs, so a row that stops contributing is a test failure rather than a silently shorter list. `scripts/verify-cordis-config.ts` continues to pass its plane-separation check against the disabled rows.
Most affected recorded-session sidecars were regenerated with `DSH_SNAPSHOT=refresh pnpm run test:snapshot`, and `pnpm run test:snapshot` replays the corpus. Six sidecars were curated by hand instead, because no local run produces them. Four belong to `pwsh-tool-turn` and `persistent-pwsh-tool-turn`, which this host skips for a missing `pwsh`; their removed text is byte-identical to what the refresh removed elsewhere. The remaining two are `snapshots/web/schedule-catalog`, which no executing test writes or reads: `schedule-after.e2e.ts` reads only that directory's `catalog.expected.md` and `session.v3.jsonl`, and `assertFixtureInventory` checks only that the four files exist, so the `header.pin: true` in its `snapshot.yml` is not enforced. `snapshots/session/ralph-loop` passes under its own composition patch, which is the evidence that the demotion removed the row from the defaults without removing coverage of the tool.
@@ -16,7 +16,7 @@ Status: implemented
`ptc` preset 还额外把 `workflow-ptc` 声明为禁用。该 preset 在去掉通用 `workflow` 工具之后只为 `ralph` 保留了这个引擎,因此禁用 `ralph` 使该组合中的引擎不再有消费方。
每个被禁用的行都在本地注释里带上恢复方法。对基于 base 的档位,用一行 overlay 即可从 `$DSH_HOME/cordis.patch.yml` 或 `--patch` 文件重新启用。preset 文件不接受补丁(`packages/preset/agent-presets/README.md`),因此想要 `ralph` 的 Web 会话要以**新 id** 把 preset 复制到 `$DSH_HOME/.agent-presets` 并删掉 `disabled`;沿用随附 id 的副本会被随附 root 遮蔽,因为重复 id 由更靠前的 root 胜出(`packages/preset/agent-presets/src/index.ts`),而 `copy()` 也拒绝任何 root 已提供的 id。在 `ptc` 中,副本要同时删掉工具行和引擎行的 `disabled`,因为 `tool-ralph` 注入 `ctx.workflowEngine`。
每个被禁用的行都在本地注释里带上恢复方法。对基于 base 的档位,用一行 overlay 即可从 `$DSH_HOME/cordis.patch.yml` 或 `--patch` 文件重新启用。随附 preset 是 Web bundle 补丁(`packages/bundle/web-app/cordis.patch.yml`)里的 `@deepseek-ai/dsh-agent-preset` 行,因此想要 `ralph` 的 Web 会话可以在 profile 补丁里覆盖该行的 `config.plugins`,或通过 Web 编辑器以**新 id** 保存一个 preset,声明会持久化到 `$DSH_HOME/profiles/<profile>/cordis.patch.yml`;注册表拒绝重复的 preset id(`packages/preset/agent-preset-registry/src/index.ts`)。在 `ptc` 中,覆盖要同时删掉工具行和引擎行的 `disabled`,因为 `tool-ralph` 注入 `ctx.workflowEngine`。
`packages/bundle/web-app/cordis.patch.yml` 在自己的层里重述这条禁用声明。`scripts/verify-cordis-config.ts` 中的 `validatePresetPlaneSeparation` 在收集已声明的行 id 时不看 `disabled`,所以删掉这一行会让 `tool-ralph` 回到 Web 宿主平面,并与每个 preset 中的同名行冲突。
@@ -44,6 +44,6 @@ Status: implemented
## 验证
`packages/preset/agent-presets/tests/shipped-root.spec.ts` 固定了四点:每个带 `tool-ralph` 的 preset 都禁用它;`minimal` 清单不含该行;`ptc` 禁用 `workflow-ptc`;`standard` 与 `cordis` 为各自的 `workflow` 工具保留引擎启用。`apps/cli/tests/web-agent-presets.e2e.ts` 与 `apps/web/tests/shipped-composition.e2e.ts` 固定默认与 PTC 的确切工具目录,因此某一行不再贡献会直接导致测试失败,而不是让列表悄悄变短。`scripts/verify-cordis-config.ts` 的平面隔离检查在禁用行存在的情况下继续通过。
`apps/cli/tests/web-agent-presets.e2e.ts` 固定了四点:每个带 `tool-ralph` 的 preset 都禁用它;`minimal` 清单不含该行;`ptc` 禁用 `workflow-ptc`;`standard` 与 `cordis` 为各自的 `workflow` 工具保留引擎启用。`apps/cli/tests/web-agent-presets.e2e.ts` 与 `apps/web/tests/shipped-composition.e2e.ts` 固定默认与 PTC 的确切工具目录,因此某一行不再贡献会直接导致测试失败,而不是让列表悄悄变短。`scripts/verify-cordis-config.ts` 的平面隔离检查在禁用行存在的情况下继续通过。
多数受影响的录制会话旁挂文件由 `DSH_SNAPSHOT=refresh pnpm run test:snapshot` 重新生成,并由 `pnpm run test:snapshot` 回放整个语料。另有六个旁挂文件是人工整理,因为本机没有任何一次运行会产出它们。其中四个属于 `pwsh-tool-turn` 与 `persistent-pwsh-tool-turn`:本机缺少 `pwsh`,这两个场景在本地被跳过;它们删掉的文本与刷新在别处删掉的文本逐字节相同。剩下两个是 `snapshots/web/schedule-catalog`,没有任何在执行中的测试写入或读取它们:`schedule-after.e2e.ts` 只读该目录的 `catalog.expected.md` 与 `session.v3.jsonl`,`assertFixtureInventory` 也只检查那四个文件存在,因此它 `snapshot.yml` 里的 `header.pin: true` 并未被强制执行。`snapshots/session/ralph-loop` 在它自己的组合补丁下通过,这证明这次降级把该行移出了默认组合,却没有移除对该工具的覆盖。
+1 -9
View File
@@ -18,15 +18,6 @@
"lib/*.js",
"lib/types/*.d.ts"
],
"dsh": {
"configTrees": [
{
"mount": "config/agent-presets",
"path": "../../packages/preset/agent-presets/presets",
"scanRoster": true
}
]
},
"license": "MIT",
"dependencies": {
"@deepseek-ai/cordis": "workspace:^",
@@ -107,6 +98,7 @@
"@deepseek-ai/dsh-mcp-resources": "workspace:^",
"@deepseek-ai/dsh-plugin-manager": "workspace:^",
"@deepseek-ai/dsh-atomic-write": "workspace:^",
"@deepseek-ai/dsh-agent-preset": "workspace:^",
"@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:^"
},
"devDependencies": {
+53 -285
View File
@@ -1,11 +1,12 @@
import { createRequire } from 'node:module'
import { randomUUID } from 'node:crypto'
import { cp, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from 'node:fs/promises'
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { dirname, join } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import {
boot,
initProfile,
createRuntimeResolution,
loadOverlayPatches,
@@ -19,7 +20,10 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
import { SUBAGENT_MODEL_SELECTION_SETTINGS_NAMESPACE } from '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
import { SETTINGS_NAMESPACE, SHIPPED_PRESET_ROOT } from '@deepseek-ai/dsh-agent-presets'
import { dump, load } from 'js-yaml'
import { entryListSchema } from '@deepseek-ai/cordis-plugin-include'
import { bundlePatchPaths, composeEntries } from '@deepseek-ai/dsh-app-boot'
const SETTINGS_NAMESPACE = 'agent-presets'
import { applyChildComposition, childSessionMeta } from '@deepseek-ai/dsh-subagent'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-compaction-basic'
@@ -30,9 +34,12 @@ import type {} from '@deepseek-ai/dsh-session-projection'
import type {} from '@deepseek-ai/dsh-token-meter'
const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
/** The shipped Web surface: the dsh-base and dsh-web-app bundle patches over an empty preset root. */
const { boot } = createRequire(import.meta.url)(join(REPO_ROOT, 'packages/boot/app-boot/lib/index.js')) as typeof import('@deepseek-ai/dsh-app-boot')
/** The shipped Web surface: the dsh-base and dsh-web-app bundle patches over an empty profile. */
const BASE_PATCH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
const WEB_PATCH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
const WEB_BUNDLE = join(REPO_ROOT, 'packages/bundle/web-app')
const WEB_PATCHES = bundlePatchPaths(WEB_BUNDLE, (JSON.parse(readFileSync(join(WEB_BUNDLE, 'package.json'), 'utf8')) as { dsh: { bundle: { patch: string[] } } }).dsh.bundle)
const webPatches = (label: string): PatchOptions[] => WEB_PATCHES.flatMap(file => loadOverlayPatches(label, file))
const CODEX_PACKAGE_DIR = join(REPO_ROOT, 'packages/subagent/subagent-codex')
const CLAUDE_CODE_PACKAGE_DIR = join(REPO_ROOT, 'packages/subagent/subagent-claude-code')
/** The installation anchor whose dependency surface the runtime resolution mirrors. */
@@ -62,8 +69,7 @@ async function bootWeb(
// The settings row defaults to `$DSH_HOME/settings.yaml`. Left alone it
// reads the developer's own document — and since the default preset is a
// setting, a stored `agent-presets.default` would decide this file's
// outcome. Point it at a temp file for the same reason the roster row
// below pins `includeUserRoot` off.
// outcome. Point it at a temporary file to isolate settings.
{ id: 'settings', config: { path: settingsFile, watch: false } },
// storage-json's root is anchored to the real $DSH_HOME. Unpinned, this
// file writes the developer's own `~/.dsh/storages/` — and then reads it
@@ -114,12 +120,7 @@ async function bootWeb(
{ id: 'directory-picker-browse', name: '@deepseek-ai/dsh-host-directory-picker-browse' },
{ id: 'ui-directory-picker-browse', name: '@deepseek-ai/dsh-client-ui-directory-picker-browse' },
] },
// Pin the roster away from the developer's machine: `includeUserRoot`
// false keeps `~/.dsh/.agent-presets` from changing a test's outcome.
// `default` here is the COMPOSITION default — the base layer the settings
// document overrides. No `roots` entry: the plugin bundles the shipped
// presets itself and prepends their root.
{ id: 'agent-presets', config: { default: 'standard', includeUserRoot: false } },
{ id: 'agent-preset-registry', config: { default: 'standard' } },
...extra,
]
const home = dirname(settingsFile)
@@ -145,7 +146,7 @@ async function bootWeb(
}
let bundlePatches: PatchOptions[] = [
...loadOverlayPatches('dsh-test', BASE_PATCH),
...loadOverlayPatches('dsh-test', WEB_PATCH),
...webPatches('dsh-test'),
]
if (profileBundles !== undefined) {
await writeFile(join(profileDir, 'package.json'), JSON.stringify({
@@ -156,6 +157,9 @@ async function bootWeb(
profile = loadProfile('dsh-test', 'spec', INSTALL_ANCHOR, home, { userLayer: false })
bundlePatches = profile.layers.flatMap(layer => layer.patches)
}
const declarations = overrides.filter(patch => 'insert' in patch)
await writeFile(profile.patchPath, dump(declarations, { schema: entryListSchema }))
const launchOverrides = overrides.filter(patch => !('insert' in patch))
const resolution = await createRuntimeResolution({ installAnchor: INSTALL_ANCHOR, home, profile })
const rootConfig = join(profileDir, 'cordis.yml')
await writeFile(rootConfig, '[]\n')
@@ -163,7 +167,7 @@ async function bootWeb(
bootCtx.provide('profileContext', { name: 'spec', dir: profileDir, patchPath: profile.patchPath,
installAnchor: INSTALL_ANCHOR, home, cwd: home,
startedBundles: profileBundles ?? ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
overlays: overrides, telemetryDisabledEnv: '1' })
overlays: launchOverrides, telemetryDisabledEnv: '1' })
await bootCtx.plugin(PluginPackages, { resolution })
bootCtx.provide('connection', {
fetch: { register: () => () => {} },
@@ -187,15 +191,13 @@ function toolParameterNames(ctx: Context, agent: Agent, toolName: string): strin
}
function enablePresetTool(composition: string, id: string): string {
const row = ` - id: ${id}\n`
const start = composition.indexOf(row)
if (start < 0) throw new Error(`missing preset row ${id}`)
const end = composition.indexOf('\n - id:', start + row.length)
const disabled = composition.indexOf(' disabled: true\n', start)
if (disabled < 0 || (end >= 0 && disabled > end)) {
throw new Error(`preset row ${id} is not disabled`)
}
return composition.slice(0, disabled) + composition.slice(disabled + ' disabled: true\n'.length)
const rows = load(composition, { schema: entryListSchema }) as import('@deepseek-ai/cordis-plugin-loader').EntryOptions[]
const visit = (entries: typeof rows): boolean => entries.some((row) => {
if (row.id === id) { row.disabled = false; return true }
return row.group === true && visit(row.config as typeof rows)
})
if (!visit(rows)) throw new Error(`missing preset row ${id}`)
return dump(rows, { schema: entryListSchema })
}
let ctx: Context
@@ -244,7 +246,7 @@ describe('the shipped Web composition', () => {
const listed = await ctx.agentPresets.list()
expect(listed.map(preset => preset.id).sort()).toEqual(['cordis', 'minimal', 'ptc', 'standard'])
expect(listed.every(preset => preset.trust === 'system')).toBe(true)
expect(listed.every(preset => !('path' in preset))).toBe(true)
expect(ctx.agentPresets.defaultId).toBe('standard')
})
@@ -369,9 +371,15 @@ describe('the shipped Web composition', () => {
expect(tools).not.toContain('str_replace_editor')
expect(ctx.commands.find(handle.agent, 'goal')).toBeDefined()
// The persona is `standard`'s, pinned verbatim so the two composition
// files cannot drift apart: tool descriptions and the skill catalog
// carry every creation-mode instruction.
// The preset's own authoring skill registers into ITS layer of the host
// registry: the cordis agent's view carries it, the global view does not.
const scoped = (await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)
expect(scoped).toContain('editing-cordis-compositions')
expect((await ctx.skills.list()).map(skill => skill.name)).not.toContain('editing-cordis-compositions')
// The persona is `standard`'s, pinned verbatim so the two declarations
// cannot drift apart: tool descriptions and the skill catalog carry
// every creation-mode instruction.
const standard = await ctx.agents.create({
sessionId: SessionId('preset-cordis-standard-persona'),
setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'standard').then(() => undefined),
@@ -385,12 +393,6 @@ describe('the shipped Web composition', () => {
} finally {
await standard.dispose()
}
// The preset's own authoring skill registers into ITS layer of the host
// registry: the cordis agent's view carries it, the global view does not.
const scoped = (await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)
expect(scoped).toContain('editing-cordis-compositions')
expect((await ctx.skills.list()).map(skill => skill.name)).not.toContain('editing-cordis-compositions')
} finally {
await handle.dispose()
}
@@ -442,11 +444,9 @@ describe('the shipped Web composition', () => {
}
})
it('ships the composition-authoring skill inside the preset directory', async () => {
// The preset's skill root is derived from its own `baseUrl`, so the skill
// travels with the directory wherever the preset is installed.
it('ships the composition-authoring skill in the declaration package', async () => {
const skill = join(
SHIPPED_PRESET_ROOT, 'cordis', 'skills', 'editing-cordis-compositions', 'SKILL.md',
REPO_ROOT, 'packages/preset/agent-preset', 'skills', 'editing-cordis-compositions', 'SKILL.md',
)
expect((await readFile(skill, 'utf8')).startsWith('---\nname: editing-cordis-compositions')).toBe(true)
@@ -513,13 +513,12 @@ describe('the shipped Web composition', () => {
}
})
it('never rewrites the preset file it composed from', async () => {
it('never rewrites the shipped profile patch when an Agent is disposed', async () => {
// The Loader persists a tree whose plugin self-disposed, and tearing an
// agent down disposes its whole subtree. Inherited, that rewrote the
// shipped composition — truncating it to `[]` the first time a session
// ended — so `PresetTree` refuses to write at all.
const path = join(SHIPPED_PRESET_ROOT, 'standard', 'agent.cordis.yml')
const before = await readFile(path, 'utf8')
const before = await Promise.all(WEB_PATCHES.map(file => readFile(file, 'utf8')))
const handle = await ctx.agents.create({
sessionId: SessionId('preset-readonly'),
@@ -533,7 +532,7 @@ describe('the shipped Web composition', () => {
// so any wait past settlement fails; a longer one only slows the test.
await new Promise(resolve => setTimeout(resolve, 50))
expect(await readFile(path, 'utf8')).toBe(before)
expect(await Promise.all(WEB_PATCHES.map(file => readFile(file, 'utf8')))).toEqual(before)
})
})
@@ -544,9 +543,10 @@ describe('product Bundle and user-preset intersection', () => {
async function bootProducts(installed: readonly Product[]): Promise<Context> {
const root = await mkdtemp(join(tmpdir(), 'dsh-product-presets-'))
const userRoot = join(root, 'presets')
const definitions: import('@deepseek-ai/cordis-plugin-loader').EntryOptions[] = []
const settingsFile = join(root, 'settings.yaml')
const standard = await readFile(join(SHIPPED_PRESET_ROOT, 'standard', 'agent.cordis.yml'), 'utf8')
const standardConfig = composeEntries([webPatches('test')]).find(row => row.id === 'preset-standard')!.config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition
const standard = dump(standardConfig.plugins, { schema: entryListSchema })
await writeFile(settingsFile, '{}\n')
for (const id of presetIds) {
let composition = standard
@@ -556,9 +556,7 @@ describe('product Bundle and user-preset intersection', () => {
if (id === 'products-claude' || id === 'products-both') {
composition = enablePresetTool(composition, 'tool-subagent-claude-code')
}
const directory = join(userRoot, id)
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'agent.cordis.yml'), composition)
definitions.push({ id: `preset-${id}`, name: '@deepseek-ai/dsh-agent-preset', config: { id, plugins: load(composition, { schema: entryListSchema }) } })
}
const packageDir = (product: Product): string => (
product === 'codex' ? CODEX_PACKAGE_DIR : CLAUDE_CODE_PACKAGE_DIR
@@ -568,17 +566,7 @@ describe('product Bundle and user-preset intersection', () => {
? '@deepseek-ai/dsh-subagent-codex'
: '@deepseek-ai/dsh-subagent-claude-code'
)
return await bootWeb(settingsFile, [
{
id: 'agent-presets',
config: {
default: 'standard',
// The shipped root is the plugin's own, prepended before this.
roots: [{ path: userRoot, trust: 'user' }],
includeUserRoot: false,
},
},
], installed.map(packageDir), [
return await bootWeb(settingsFile, [{ insert: definitions } ], installed.map(packageDir), [
'@deepseek-ai/dsh-base',
'@deepseek-ai/dsh-web-app',
...installed.map(packageName),
@@ -637,38 +625,9 @@ describe('product Bundle and user-preset intersection', () => {
}
}
}, 120_000)
it('applies a product-row edit only to later sessions on the preset', async () => {
const productCtx = await bootProducts(['codex'])
const preset = await productCtx.agentPresets.resolve('products-none')
const original = await readFile(preset.path, 'utf8')
const existing = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-existing'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
await writeFile(preset.path, enablePresetTool(original, 'tool-subagent-codex'))
const later = await productCtx.agents.create({
sessionId: SessionId('preset-product-generation-later'),
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, 'products-none').then(() => undefined),
})
try {
expect(toolNames(productCtx, existing.agent)).not.toContain('subagent_codex')
expect(toolNames(productCtx, later.agent)).toContain('subagent_codex')
} finally {
await later.dispose()
}
} finally {
await existing.dispose()
await writeFile(preset.path, original)
await productCtx.fiber.dispose()
}
}, 120_000)
})
describe('a user preset copied from a shipped one', () => {
describe('a user preset declared from the shipped cordis rows', () => {
/** The JSON text a `cordis_inspect_*` result renders. */
function resultText(result: { content: ReadonlyArray<{ type: string; text?: string }> }): string {
return result.content.filter(part => part.type === 'text').map(part => part.text ?? '').join('')
@@ -680,14 +639,13 @@ describe('a user preset copied from a shipped one', () => {
// `tool-cordis` row only registers the tools. Before that split the copy
// failed to mount: its row re-registered provider "Service".
const root = await mkdtemp(join(tmpdir(), 'dsh-copied-preset-'))
const userRoot = join(root, 'presets')
await cp(join(SHIPPED_PRESET_ROOT, 'cordis'), join(userRoot, 'cordis-copy'), { recursive: true })
const settingsFile = join(root, 'settings.yaml')
await writeFile(settingsFile, '{}\n')
const copyCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: { default: 'standard', roots: [{ path: userRoot, trust: 'user' }], includeUserRoot: false },
}])
const cordis = composeEntries([webPatches('test')]).find(row => row.id === 'preset-cordis')!.config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition
const copyCtx = await bootWeb(settingsFile, [{ insert: [{
id: 'preset-cordis-copy', name: '@deepseek-ai/dsh-agent-preset',
config: { ...cordis, id: 'cordis-copy', name: 'Cordis copy' },
}] }])
try {
const shipped = await copyCtx.agents.create({
sessionId: SessionId(`preset-cordis-shipped-${randomUUID()}`),
@@ -851,137 +809,6 @@ describe('a delegated child', () => {
})
})
describe('a launcher that configures no writable root', () => {
// The claim this default exists for, asserted through the real shipped
// bundles rather than a hand-built context: `apps/cli` patches in only the
// system root, and a person's own presets are found anyway because the
// roster derives `<dshHome>/.agent-presets` itself. `$DSH_HOME` is pointed
// at a temp home BEFORE boot — the derived root is resolved when the plugin
// is constructed, and an unpinned run would read the developer's own.
let derivedCtx: Context
let previousHome: string | undefined
beforeAll(async () => {
const home = await mkdtemp(join(tmpdir(), 'dsh-preset-derived-'))
previousHome = process.env.DSH_HOME
process.env.DSH_HOME = home
await mkdir(join(home, '.agent-presets', 'derived-mine'), { recursive: true })
await writeFile(
join(home, '.agent-presets', 'derived-mine', 'agent.cordis.yml'),
'- id: tool-todo\n name: \'@deepseek-ai/dsh-tool-todo\'\n config:\n allowParallelInProgress: true\n',
)
const settingsFile = join(await mkdtemp(join(tmpdir(), 'dsh-preset-derived-settings-')), 'settings.yaml')
await writeFile(settingsFile, '{}\n')
// No configured roots: the shipped one is the plugin's own, and the
// writable one is the roster's own default rather than this patch's job.
derivedCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: { default: 'standard', includeUserRoot: true },
}])
}, 120_000)
afterAll(async () => {
if (previousHome === undefined) delete process.env.DSH_HOME
else process.env.DSH_HOME = previousHome
await derivedCtx.fiber.dispose()
})
it('discovers and mounts a preset the person authored under the harness home', async () => {
const listed = await derivedCtx.agentPresets.list()
const mine = listed.find(preset => preset.id === 'derived-mine')
expect(mine).toMatchObject({ trust: 'user' })
// Omitted rather than undefined: a healthy row carries no `broken` key.
expect(mine?.broken).toBeUndefined()
expect(derivedCtx.agentPresets.authorable).toBe(true)
const handle = await derivedCtx.agents.create({
sessionId: SessionId('preset-derived-root'),
setup: agentCtx => derivedCtx.agentPresets.mount(agentCtx, 'derived-mine').then(() => undefined),
})
try {
expect(toolNames(derivedCtx, handle.agent)).toContain('todo_write')
} finally {
await handle.dispose()
}
})
})
describe('authoring a preset on the shipped composition', () => {
let authorCtx: Context
let userRoot: string
beforeAll(async () => {
userRoot = join(await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-')), 'profiles')
const settingsFile = join(await mkdtemp(join(tmpdir(), 'dsh-preset-authoring-settings-')), 'settings.yaml')
await writeFile(settingsFile, '{}\n')
authorCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: {
default: 'standard',
// The root does not exist yet: a deployment whose user has authored
// nothing is the normal first-run state. The shipped root is the
// plugin's own, prepended before this.
roots: [{ path: userRoot, trust: 'user' }],
includeUserRoot: false,
},
}])
})
it('refuses to copy over or delete a shipped preset', async () => {
await expect(authorCtx.agentPresets.copy('minimal', 'standard')).rejects.toThrow(/already exists/)
await expect(authorCtx.agentPresets.remove('standard')).rejects.toThrow(/ships with the deployment/)
})
it.each(['../escape', 'a/b', '/abs', 'Upper'])('refuses the uncontainable id %j', async (id) => {
// The id becomes a directory name under the user root, so containment is
// checked on the id rather than on the joined path afterwards.
await expect(authorCtx.agentPresets.copy('minimal', id)).rejects.toThrow()
})
it('copies a shipped preset a session then really composes from', async () => {
await authorCtx.agentPresets.copy('minimal', 'my-agent', '我的模式')
// Round-trips through the roster as a `user` row carrying the given name
// and the source's description, over the source's own composition text.
const preset = await authorCtx.agentPresets.resolve('my-agent')
const source = await authorCtx.agentPresets.resolve('minimal')
expect(preset.trust).toBe('user')
expect(preset.name).toBe('我的模式')
expect(preset.description).toBe(source.description)
expect(await authorCtx.agentPresets.read('my-agent')).toBe(await authorCtx.agentPresets.read('minimal'))
// Owner-only, in an owner-only directory: a composition is executable
// configuration on a machine that may have other users.
expect((await stat(preset.path)).mode & 0o777).toBe(0o600)
const handle = await authorCtx.agents.create({
sessionId: SessionId('preset-authored'),
setup: agentCtx => authorCtx.agentPresets.mount(agentCtx, 'my-agent').then(() => undefined),
})
try {
// The same tools the shipped `minimal` composes, from a directory copied
// through the service into a root outside the installed harness.
expect(toolNames(authorCtx, handle.agent)).toEqual(['bash'])
} finally {
await handle.dispose()
}
})
it('deletes what it copied', async () => {
await authorCtx.agentPresets.copy('minimal', 'doomed')
await authorCtx.agentPresets.remove('doomed')
expect((await authorCtx.agentPresets.list()).map(preset => preset.id)).not.toContain('doomed')
})
})
/**
* Which preset an unnamed session gets is a user setting layered over the
* composition's own default. The package suite proves the layering against a
* hand-built context; this proves it through the shipped `cordis.yml` — that
* the roster and the settings provider are actually wired to each other, and
* that the id the setting names is the one a session composes from.
*/
describe('the default preset as a user setting', () => {
it('composes an unnamed session from the stored default, not the composed one', async () => {
expect((await ctx.agentPresets.remoteExportList()).modeSelectionEnabled).toBe(true)
@@ -1031,63 +858,4 @@ describe('a session keeps the preset it was created with', () => {
})
})
describe('a composition that configures its own preset roots', () => {
let rootsCtx: Context
let teamRoot: string
beforeAll(async () => {
const home = await mkdtemp(join(tmpdir(), 'dsh-preset-roots-'))
const settingsFile = join(home, 'settings.yaml')
await writeFile(settingsFile, '{}\n')
// A workspace-shared root beside the deployment: one preset of its own,
// plus a directory that claims a shipped id.
teamRoot = join(home, 'team-presets')
const minimalComposition = await readFile(join(SHIPPED_PRESET_ROOT, 'minimal', 'agent.cordis.yml'), 'utf8')
for (const id of ['team-spec', 'minimal']) {
await mkdir(join(teamRoot, id), { recursive: true })
await writeFile(join(teamRoot, id, 'agent.cordis.yml'), minimalComposition)
}
// The user layer of the reported regression: a profile's cordis.patch.yml
// configuring a shared preset root. The plugin must EXTEND it with its
// own shipped root, never lose it.
rootsCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: {
default: 'standard',
roots: [{ path: teamRoot, trust: 'user' }],
includeUserRoot: false,
},
}])
}, 120_000)
afterAll(async () => {
await rootsCtx.fiber.dispose()
})
it('keeps configured roots alongside the always-prepended shipped root', async () => {
expect(rootsCtx.agentPresets.roots.map(root => root.path)).toEqual([
SHIPPED_PRESET_ROOT,
teamRoot,
])
const listed = await rootsCtx.agentPresets.list()
expect(listed.map(preset => preset.id).sort()).toEqual(['cordis', 'minimal', 'ptc', 'standard', 'team-spec'])
expect(listed.every(preset => preset.broken === undefined)).toBe(true)
// The shipped root comes first: a configured directory claiming a shipped
// id is shadowed, never the other way around.
expect(listed.find(preset => preset.id === 'minimal')?.trust).toBe('system')
expect(listed.find(preset => preset.id === 'team-spec')?.trust).toBe('user')
})
it('composes an agent from a configured-root preset', async () => {
const handle = await rootsCtx.agents.create({
sessionId: SessionId('preset-team-spec'),
setup: agentCtx => rootsCtx.agentPresets.mount(agentCtx, 'team-spec').then(() => undefined),
})
try {
expect(toolNames(rootsCtx, handle.agent)).toEqual(['bash'])
} finally {
await handle.dispose()
}
})
})
afterAll(async () => { await ctx?.fiber.dispose() })
+9 -11
View File
@@ -18,8 +18,7 @@ import { fileURLToPath } from 'node:url'
import yaml from 'js-yaml'
import { entryListSchema } from '@deepseek-ai/cordis-plugin-include'
import { evaluate } from '@deepseek-ai/cordis-plugin-loader'
import { SHIPPED_PRESET_ROOT } from '@deepseek-ai/dsh-agent-presets'
import { composeEntries, initProfile, loadProfile, PROFILES_DIR } from '@deepseek-ai/dsh-app-boot'
import { bundlePatchPaths, composeEntries, initProfile, loadProfile, PROFILES_DIR } from '@deepseek-ai/dsh-app-boot'
/**
* The effective disabled state of one row on one platform: a `!!js` expression
@@ -102,13 +101,15 @@ describe('the shipped shell composition (real bundle layers)', () => {
})
describe('shipped agent presets gate both shell tools by platform', () => {
const presetRoot = SHIPPED_PRESET_ROOT
const webBundle = fileURLToPath(new URL('../../../packages/bundle/web-app/', import.meta.url))
const webManifest = JSON.parse(readFileSync(join(webBundle, 'package.json'), 'utf8')) as { dsh: { bundle: { patch: string[] } } }
const presetRows = composeEntries([bundlePatchPaths(webBundle, webManifest.dsh.bundle).flatMap(file =>
yaml.load(readFileSync(file, 'utf8'), { schema: entryListSchema }) as import('@deepseek-ai/cordis-plugin-include').PatchOptions[])])
const definitions = presetRows.filter(row => row.name === '@deepseek-ai/dsh-agent-preset').map(row => row.config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition)
it.each(['standard', 'ptc', 'cordis'])('preset %s gates its shell tool rows by platform', (preset) => {
const entries: unknown = yaml.load(
readFileSync(join(presetRoot, preset, 'agent.cordis.yml'), 'utf8'),
{ schema: entryListSchema },
)
const entries: unknown = definitions.find(row => row.id === preset)!.plugins
if (!Array.isArray(entries)) throw new TypeError(`preset ${preset} must parse to an entry array`)
for (const [id, win32] of [['tool-bash', true], ['tool-pwsh', false]] as const) {
const row = entries.find((entry): entry is Record<string, unknown> => (
@@ -124,10 +125,7 @@ describe('shipped agent presets gate both shell tools by platform', () => {
})
it('minimal mounts no shell tool row and gates its persistent shell stack by platform', () => {
const entries: unknown = yaml.load(
readFileSync(join(presetRoot, 'minimal', 'agent.cordis.yml'), 'utf8'),
{ schema: entryListSchema },
)
const entries: unknown = definitions.find(row => row.id === 'minimal')!.plugins
if (!Array.isArray(entries)) throw new TypeError('minimal preset must parse to an entry array')
for (const id of ['tool-bash', 'tool-pwsh']) {
expect(entries.some(entry => (
+1 -1
View File
@@ -28,7 +28,7 @@ try {
{ id: 'session-title-llm', disabled: true },
{ id: 'session-telemetry-otel', disabled: true },
{ id: 'agent-instructions', disabled: true },
{ id: 'agent-presets', config: { default: 'standard', includeUserRoot: false } },
{ id: 'agent-preset-registry', config: { default: 'standard' } },
{ insert: [{ id: 'update-qualification', name: new URL('../tests/fixtures/host-update-control.mjs', import.meta.url).href }] },
]))
const environment = Object.fromEntries(Object.entries(process.env).filter(([name]) =>
@@ -52,7 +52,7 @@ try {
{ id: 'webserver', config: { host: '127.0.0.1', port: 0 } },
{ id: 'llm-deepseek', disabled: true }, { id: 'session-title-llm', disabled: true },
{ id: 'session-telemetry-otel', disabled: true },
{ id: 'agent-presets', config: { default: 'standard', includeUserRoot: false } },
{ id: 'agent-preset-registry', config: { default: 'standard' } },
{ insert: [{ id: 'update-control', name: new URL('../tests/fixtures/workspace-update-host.mjs', import.meta.url).href }] },
]))
const environment = Object.fromEntries(Object.entries(process.env).filter(([name]) =>
+39 -284
View File
@@ -1,315 +1,70 @@
// Web e2e scenario: the agent-preset settings section as copy-only authoring.
// The browser never edits composition text — a shipped preset opens in a
// read-only viewer, the copy dialog collects an id and an optional display
// name, and the host copies the whole directory. The section's other job is
// getting the user TO the files: this lane pins `nativeOpen: false` (see the
// overlay), so the location affordance answers the preset directory as text —
// the deterministic branch a golden can hold on every platform.
//
// Zero model calls: no replay fixture mounts, so a stray stream fails loud.
import { existsSync } from 'node:fs'
import { mkdir, mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
/** The preset settings page only views and selects; authoring is guided to Creator mode. */
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import type { Locator } from 'playwright'
import {
captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole,
webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { ZH_BROWSER_LOCALE, connectFreshWorkspaceZh, saveFailureShot } from './support.ts'
import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold } from './scaffold.ts'
import { ZH_BROWSER_LOCALE, saveFailureShot } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('./expected/agent-preset-authoring', import.meta.url))
const SECTION_EXPECTED = join(SNAPSHOT_DIR, 'section.expected.md')
const COPY_DIALOG_EXPECTED = join(SNAPSHOT_DIR, 'copy-dialog.expected.md')
const CREATED_EXPECTED = join(SNAPSHOT_DIR, 'created.expected.md')
const DAMAGED_EXPECTED = join(SNAPSHOT_DIR, 'damaged.expected.md')
/** The shipped roster, bundled inside the `dsh-agent-presets` package. */
const SHIPPED_PRESETS = fileURLToPath(new URL('../../../packages/preset/agent-presets/presets', import.meta.url))
const OVERLAY = fileURLToPath(new URL('./agent-preset-authoring.overlay.yml', import.meta.url))
const MODE = webSnapshotMode()
const EXPECTED = fileURLToPath(new URL('./expected/agent-preset-authoring', import.meta.url))
const mode = webSnapshotMode()
describe('web e2e: agent-preset authoring is a host-side copy', () => {
describe('web e2e: preset roster guidance', () => {
let scaffold: WebScaffold
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let userRoot: string
/** The settings dialog, opened on the Agent-presets section. */
function settingsDialog(): Locator {
return page.getByRole('dialog', { name: '设置' })
}
beforeAll(async () => {
userRoot = await realpath(await mkdtemp(join(tmpdir(), 'dsh-web-e2e-presets-')))
scaffold = await launchWebScaffold({
extraOverlayPath: OVERLAY,
profile: { packages: [] },
agentPresets: {
// The shipped root is the plugin's own, prepended before this.
roots: [{ path: userRoot, trust: 'user' }],
default: 'standard',
},
})
scaffold = await launchWebScaffold({ profile: { packages: [] } })
browser = await chromium.launch()
// The scenario asserts the shipped Chinese copy, so the browser asks for it.
page = await browser.newPage({ viewport: { width: 1680, height: 1000 }, locale: ZH_BROWSER_LOCALE })
tripwire = watchConsole(page)
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
await page.getByRole('button', { name: '设置', exact: true }).click()
await page.getByRole('dialog', { name: '设置' }).getByRole('button', { name: 'Agent 预设' }).click()
await page.getByRole('heading', { name: 'Agent 预设' }).waitFor()
}, 120_000)
afterAll(async () => { await browser?.close(); await scaffold?.close() })
afterAll(async () => {
await browser?.close()
await scaffold?.close()
await rm(userRoot, { recursive: true, force: true })
it('shows the shipped roster with mode help and no editing actions', async () => {
onTestFailed(() => saveFailureShot(page, 'preset-roster-section'))
await expect.poll(() => page.locator('[data-agent-preset-id]').count()).toBe(4)
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(join(EXPECTED, 'section.expected.md'), snapshot, mode)
expect(snapshot).toContain('如何创建或修改预设')
expect(snapshot).not.toContain('新建预设')
expect(snapshot).not.toContain('编辑插件')
expect(snapshot).not.toContain('打开目录')
expect(snapshot).not.toContain('删除')
})
it('offers the roster with copy as the only way to create', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-section'))
await page.getByRole('button', { name: '设置', exact: true }).click()
const dialog = settingsDialog()
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: 'Agent 预设' }).click()
await dialog.getByRole('heading', { name: 'Agent 预设' }).waitFor({ timeout: 10_000 })
// The intro copy also names 标准模式. Wait for the roster's own action so
// the snapshot cannot land between the section shell and its cards.
await dialog.getByRole('button', { name: '查看配置: 标准模式', exact: true }).waitFor({ timeout: 10_000 })
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(SECTION_EXPECTED, snapshot, MODE)
const toggle = dialog.getByRole('switch', { name: '新任务可选择模式' })
expect(await toggle.getAttribute('aria-checked')).toBe('true')
// The authoring entry is visible, and the shipped rows offer
// view/copy but never delete or a location — their
// install is overwritten by upgrades and is not the user's to manage.
expect(snapshot).toContain('让 Agent 帮我创建预设模式')
expect(snapshot).not.toContain('新建预设')
expect(snapshot).toContain('查看配置: 标准模式')
expect(snapshot).not.toContain('删除: 标准模式')
expect(snapshot).not.toContain('打开目录')
// The rest of this scenario exercises the existing default and Creator
// actions with the beta picker enabled by default.
}, 60_000)
it('reads technical details and examples without changing the new-task default', async () => {
const settings = settingsDialog()
it('reads mode details and examples without changing the new-task default', async () => {
onTestFailed(() => saveFailureShot(page, 'preset-roster-guide'))
const settings = page.getByRole('dialog', { name: '设置' })
const trigger = settings.getByRole('button', { name: '模式说明: PTC 模式', exact: true })
await trigger.click()
const guide = page.getByRole('dialog', { name: 'PTC 模式', exact: true })
await guide.getByRole('heading', { name: '怎样调用工具', exact: true }).waitFor()
const beforeSwitch = await guide.boundingBox()
expect(await guide.textContent()).toContain('run_code')
await guide.getByRole('tab', { name: '如何使用', exact: true }).click()
await guide.getByRole('heading', { name: '批量检查配置文件', exact: true }).waitFor()
expect(await guide.boundingBox()).toEqual(beforeSwitch)
await guide.getByRole('tab', { name: '如何使用', exact: true }).press('ArrowLeft')
expect(await guide.getByRole('tab', { name: '模式说明', exact: true }).getAttribute('aria-selected')).toBe('true')
await guide.getByRole('tab', { name: '模式说明', exact: true }).press('Escape')
await guide.getByRole('tab', { name: '如何使用', exact: true }).press('Escape')
await guide.waitFor({ state: 'detached' })
expect(await trigger.evaluate(element => document.activeElement === element)).toBe(true)
expect(await settings.isVisible()).toBe(true)
expect(await settings.getByRole('button', { name: '新任务默认: 标准模式', exact: true }).getAttribute('aria-pressed')).toBe('true')
await settings.getByRole('button', { name: '如何使用: 创造模式', exact: true }).click()
const creator = page.getByRole('dialog', { name: '创造模式', exact: true })
await creator.getByRole('heading', { name: '添加一个界面', exact: true }).waitFor()
expect(await creator.getByRole('heading', { name: '添加一个工具', exact: true }).isVisible()).toBe(true)
const closePosition = await creator.getByRole('button', { name: '关闭', exact: true }).boundingBox()
const examples = creator.getByRole('tabpanel', { name: '如何使用', exact: true })
const scrolledTo = await examples.evaluate((element) => {
element.scrollTop = element.scrollHeight
return element.scrollTop
})
expect(scrolledTo).toBeGreaterThan(0)
expect(await creator.getByRole('button', { name: '关闭', exact: true }).boundingBox()).toEqual(closePosition)
await creator.getByRole('tab', { name: '模式说明', exact: true }).click()
await creator.getByRole('tab', { name: '如何使用', exact: true }).click()
expect(await examples.evaluate(element => element.scrollTop)).toBe(scrolledTo)
await creator.getByRole('button', { name: '关闭', exact: true }).click()
await creator.waitFor({ state: 'detached' })
}, 60_000)
it('views a shipped composition read-only instead of editing it', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-view'))
const dialog = settingsDialog()
await dialog.getByRole('button', { name: '查看配置: 标准模式' }).click()
const viewer = page.getByRole('dialog', { name: '查看配置 · 标准模式' })
await viewer.waitFor({ timeout: 10_000 })
// The real shipped composition, not a golden: the viewer shows whatever
// the deployment ships, and this lane only asserts it is shown read-only.
const shipped = await readFile(join(SHIPPED_PRESETS, 'standard', 'agent.cordis.yml'), 'utf8')
expect(await viewer.locator('pre').textContent()).toBe(shipped)
expect(await viewer.getByRole('textbox').count()).toBe(0)
// The header X and the footer button share the 关闭 name; the footer one
// is last in the dialog.
await viewer.getByRole('button', { name: '关闭' }).last().click()
await viewer.waitFor({ state: 'detached', timeout: 10_000 })
}, 60_000)
it('copies 极简模式 whole under a new id and lands in its files', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-copy'))
const dialog = settingsDialog()
await dialog.getByRole('button', { name: '复制预设: 极简模式' }).click()
const copyDialog = page.getByRole('dialog', { name: '复制预设 · 复制自 极简模式' })
await copyDialog.waitFor({ timeout: 10_000 })
const dialogSnapshot = await captureStableAria(
page, '[role="dialog"][aria-label^="复制预设"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(COPY_DIALOG_EXPECTED, dialogSnapshot, MODE)
// Two fields and nothing else: the id is the directory name the host
// needs up front; description and composition live in the files.
expect(dialogSnapshot).toContain('标识符')
expect(dialogSnapshot).not.toContain('描述')
await copyDialog.getByPlaceholder('my-agent').fill('my-agent')
await copyDialog.getByPlaceholder('在模式列表中显示的名称,不填则使用标识符').fill('我的模式')
await copyDialog.getByRole('button', { name: '创建' }).click()
await copyDialog.waitFor({ state: 'detached', timeout: 10_000 })
// The new row lands in the custom group, and — with no desktop opener —
// its directory is revealed as text right away: landing in the files is
// the completion of a copy, not a follow-up.
await dialog.getByText('我的模式').first().waitFor({ timeout: 10_000 })
await dialog.getByText('预设文件:').waitFor({ timeout: 10_000 })
// The copy dialog is detached, so the settings dialog is the only one
// left (it names itself via aria-labelledby, which a CSS attribute
// selector cannot address).
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd, {
replacements: [[userRoot, '{{presetRoot}}']],
})
await compareOrRefreshGolden(CREATED_EXPECTED, snapshot, MODE)
expect(snapshot).toContain('{{presetRoot}}/my-agent')
// The host copied the whole directory and rewrote only the display
// metadata: the composition is byte-identical to the shipped source, the
// description rides along for the user to edit in place, and neither the
// source's name nor its roster order survives into the copy.
const composition = await readFile(join(userRoot, 'my-agent', 'agent.cordis.yml'), 'utf8')
expect(composition).toBe(await readFile(join(SHIPPED_PRESETS, 'minimal', 'agent.cordis.yml'), 'utf8'))
const metadata = await readFile(join(userRoot, 'my-agent', 'preset.yml'), 'utf8')
expect(metadata).toContain('name: 我的模式')
expect(metadata).toContain('description: 仅提供持久 shell 的单工具编码 Agent。')
expect(metadata).not.toContain('order:')
}, 60_000)
it('deletes the copy after confirmation and reclaims the roster', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-delete'))
const dialog = settingsDialog()
await dialog.getByRole('button', { name: '删除: 我的模式' }).click()
const confirm = page.getByRole('dialog', { name: '删除该预设?' })
await confirm.waitFor({ timeout: 10_000 })
await confirm.getByRole('button', { name: '删除', exact: true }).click()
await confirm.waitFor({ state: 'detached', timeout: 10_000 })
await expect.poll(async () => dialog.getByText('我的模式').count(), { timeout: 10_000 }).toBe(0)
expect(existsSync(join(userRoot, 'my-agent'))).toBe(false)
// The custom group outlives its only member: the heading stays with the
// creator entry so the place to author a preset never disappears.
expect(await dialog.getByRole('heading', { name: '自定义' }).count()).toBe(1)
expect(await dialog.getByRole('button', { name: '让 Agent 帮我创建预设模式' }).count()).toBe(1)
expect(await dialog.getByText('标准模式').count()).toBeGreaterThan(0)
}, 60_000)
it('marks damaged presets broken and clears a ghost through delete', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-damaged'))
// The two hand-edit damage shapes: a composition that no longer parses,
// and a directory whose composition file was deleted outright.
await mkdir(join(userRoot, 'broken-yaml'), { recursive: true })
await writeFile(join(userRoot, 'broken-yaml', 'agent.cordis.yml'), '- id: x\n name: [unclosed\n')
await mkdir(join(userRoot, 'ghost'), { recursive: true })
await writeFile(join(userRoot, 'ghost', 'preset.yml'), 'name: 幽灵预设\ndescription: composition 已被手动删除。\n')
// The section reads the roster when it mounts; hop away and back.
const dialog = settingsDialog()
await dialog.getByRole('button', { name: '通用设置' }).click()
await dialog.getByRole('button', { name: 'Agent 预设' }).click()
await dialog.getByText('加载失败').first().waitFor({ timeout: 10_000 })
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd, {
replacements: [[userRoot, '{{presetRoot}}']],
})
await compareOrRefreshGolden(DAMAGED_EXPECTED, snapshot, MODE)
// Both damage shapes surface as marked, unselectable, uncopyable cards
// that still carry their metadata and the discovery-reported reason.
expect(snapshot).toContain('加载失败: broken-yaml')
expect(snapshot).toContain('加载失败: 幽灵预设')
expect(snapshot).toContain('not valid YAML')
expect(snapshot).toContain('agent.cordis.yml is missing')
expect(await dialog.getByRole('button', { name: '加载失败: broken-yaml' }).isDisabled()).toBe(true)
expect(await dialog.getByRole('button', { name: '复制预设: 幽灵预设' }).isDisabled()).toBe(true)
// A broken card offers no "set default" affordance at all — the aria name
// IS the broken marking, so the picking name must not exist.
expect(await dialog.getByRole('button', { name: '设为新任务默认: broken-yaml' }).count()).toBe(0)
// The ghost's way out is the card's own delete — and the id it blocked
// is claimable again immediately afterwards.
await dialog.getByRole('button', { name: '删除: 幽灵预设' }).click()
const confirm = page.getByRole('dialog', { name: '删除该预设?' })
await confirm.waitFor({ timeout: 10_000 })
await confirm.getByRole('button', { name: '删除', exact: true }).click()
await confirm.waitFor({ state: 'detached', timeout: 10_000 })
await expect.poll(async () => dialog.getByText('幽灵预设').count(), { timeout: 10_000 }).toBe(0)
expect(existsSync(join(userRoot, 'ghost'))).toBe(false)
await dialog.getByRole('button', { name: '复制预设: 极简模式' }).click()
const copyDialog = page.getByRole('dialog', { name: '复制预设 · 复制自 极简模式' })
await copyDialog.waitFor({ timeout: 10_000 })
await copyDialog.getByPlaceholder('my-agent').fill('ghost')
await copyDialog.getByRole('button', { name: '创建' }).click()
await copyDialog.waitFor({ state: 'detached', timeout: 10_000 })
await dialog.getByRole('button', { name: '设为新任务默认: ghost' }).waitFor({ timeout: 10_000 })
// Leave the roster as the earlier tests shaped it.
await dialog.getByRole('button', { name: '删除: ghost' }).click()
const cleanup = page.getByRole('dialog', { name: '删除该预设?' })
await cleanup.waitFor({ timeout: 10_000 })
await cleanup.getByRole('button', { name: '删除', exact: true }).click()
await cleanup.waitFor({ state: 'detached', timeout: 10_000 })
await rm(join(userRoot, 'broken-yaml'), { recursive: true, force: true })
}, 60_000)
it('starts a creator-mode session from the section', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-preset-authoring-creator'))
// Without a workspace the flow only stages (there is no session to land
// in until one is connected); connect first so the gesture carries all
// the way to a composed host session.
await settingsDialog().getByRole('button', { name: '关闭' }).last().click()
await connectFreshWorkspaceZh(page, scaffold.workspaceCwd)
await page.getByRole('button', { name: '设置', exact: true }).click()
const dialog = settingsDialog()
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: 'Agent 预设' }).click()
await dialog.getByRole('button', { name: '让 Agent 帮我创建预设模式' }).click()
// Leaving settings is part of the gesture: the flow lands on the
// new-session screen with the self-referential preset staged, and the
// blank session the flow produces composes from it on the host.
await dialog.waitFor({ state: 'detached', timeout: 10_000 })
await expect.poll(async () => {
const response = await scaffold.hostFetch('/api/session/list', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
type: 'client-request', rpcId: 'creator-draft-stage', method: 'session/list',
payload: { args: { _request: {} } },
}),
})
const body = await response.json() as {
result: { value?: { items: unknown[] } }
}
return JSON.stringify(body.result.value?.items ?? body.result)
}, { timeout: 15_000 }).toContain('"agentPreset":"cordis"')
}, 60_000)
it('drove every surface without a page error or a stream warning', () => {
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
})
it('guides authoring to Creator mode instead of editing here', async () => {
onTestFailed(() => saveFailureShot(page, 'preset-roster-authoring-help'))
await page.getByRole('button', { name: '如何创建或修改预设', exact: true }).click()
const help = page.getByRole('dialog', { name: '创建和修改预设', exact: true })
await help.waitFor()
const snapshot = await captureStableAria(page, '[role="dialog"][aria-label="创建和修改预设"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(join(EXPECTED, 'authoring-help.expected.md'), snapshot, mode)
expect(snapshot).toContain('让 Agent 帮我创建预设模式')
await help.getByRole('button', { name: '关闭', exact: true }).last().click()
await help.waitFor({ state: 'detached' })
})
it('runs without page errors or model calls', () => { expect(tripwire.pageErrors).toEqual([]) })
})
@@ -1,13 +0,0 @@
# The authoring lane drives the location affordance. A real desktop open
# would pop a file manager on the machine running the tests and the
# capability itself is platform-detected (macOS yes, headless Linux CI no),
# so both native-open owners are pinned headless: `hasDocument` is false everywhere and
# `openDocument` answers the directory as text — the same branch on every
# host, and the one whose rendering a golden can hold. A patch replaces the
# row's complete config, so the shipped routing defaults ride along.
- id: session-controller
config:
nativeOpen: false
- id: settings-controller
config:
nativeOpen: false
+16 -31
View File
@@ -8,7 +8,7 @@
// because the host answers `agent-preset-locked` to anything else.
//
// Zero model calls: no replay fixture mounts, so a stray stream fails loud.
import { fileURLToPath } from 'node:url'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
@@ -42,22 +42,15 @@ const SKILL_NAME = 'preset-catalog-demo'
/** The preset whose rows resolve and then refuse to start. */
const REFUSING_ID = 'zz-refusing'
/**
* Seed a preset discovery reports healthy and the mount refuses.
*
* Every row resolves — the module is right there beside the composition — so
* health has nothing to report and the chip offers the preset like any other.
* Only starting it finds out, which is the case the chip's banner exists for.
* @param root - the lane's writable preset root.
/** Write the fixture module whose declaration fails during eager activation.
* @param root Temporary directory holding the fixture module.
*/
async function seedRefusingPreset(root: string): Promise<void> {
const directory = join(root, REFUSING_ID)
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'refuses.mjs'),
'export const name = \'refuses\'\nexport function apply() { throw new Error(\'this row refuses to start\') }\n')
await writeFile(join(directory, 'agent.cordis.yml'), '- id: refuses\n name: ./refuses.mjs\n')
await writeFile(join(directory, 'preset.yml'),
'name: Refusing mode\ndescription: Resolves, then refuses to start.\n')
}
/**
@@ -228,15 +221,14 @@ describe('web e2e: agent-preset selection', () => {
let browser: Browser
let page: Page
let tripwire: ReturnType<typeof watchConsole>
let presetRoot: string
let fixtureRoot: string
beforeAll(async () => {
// The shipped presets, plus one lane-owned preset that mounts and refuses:
// the chip's own failure path needs a preset the roster offers.
presetRoot = await realpath(await mkdtemp(join(tmpdir(), 'dsh-web-e2e-refusing-')))
await seedRefusingPreset(presetRoot)
// The failed declaration remains in the registry, outside the selectable options.
fixtureRoot = await realpath(await mkdtemp(join(tmpdir(), 'dsh-web-e2e-refusing-')))
await seedRefusingPreset(fixtureRoot)
scaffold = await launchWebScaffold({
agentPresets: { roots: [{ path: presetRoot, trust: 'user' }], default: 'standard' },
agentPresets: { default: 'standard', definitions: [{ id: REFUSING_ID, name: 'Refusing mode', description: 'Refuses to start.', plugins: [{ name: pathToFileURL(join(fixtureRoot, REFUSING_ID, 'refuses.mjs')).href }] }] },
})
// A resumed session runs what it was created with; seeding one that
// records `minimal` is what makes the header label a claim about the
@@ -254,7 +246,7 @@ describe('web e2e: agent-preset selection', () => {
afterAll(async () => {
await browser?.close()
await scaffold?.close()
await rm(presetRoot, { recursive: true, force: true })
await rm(fixtureRoot, { recursive: true, force: true })
})
it('starts with mode selection shown on the Standard default', async () => {
@@ -303,20 +295,13 @@ describe('web e2e: agent-preset selection', () => {
expect(roster.presets.find(preset => preset.isDefault)?.id).toBe('standard')
})
it('says why a switch was refused instead of letting the chip revert in silence', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-preset-refused'))
it('omits eagerly failed presets from selection and retains their diagnostics', async () => {
await page.getByRole('button', { name: 'Minimal mode' }).click()
await page.getByRole('menuitem', { name: /Refusing mode/ }).click()
// Health cleared every row, so nothing on the settings page says this
// preset is unusable — the banner is where the host's reason lands, and
// without it the chip just snaps back to the preset it already ran.
const banner = page.getByRole('alert').filter({ hasText: 'Refusing mode' })
await banner.waitFor({ timeout: 15_000 })
expect(await banner.textContent()).toContain('this row refuses to start')
await expect.poll(() => livePreset(scaffold), { timeout: 15_000 }).toBe('minimal')
await page.getByRole('button', { name: 'Minimal mode' }).waitFor({ timeout: 10_000 })
}, 60_000)
await page.getByRole('menu').waitFor()
expect(await page.getByRole('menuitem', { name: /Refusing mode/ }).count()).toBe(0)
await page.keyboard.press('Escape')
expect((await scaffold.ctx.agentPresets.resolve(REFUSING_ID)).broken).toContain('this row refuses to start')
})
it('re-reads the slash catalog through the composition the switch installed', async () => {
// Continues 'applies the staged pick': the chip has already applied `minimal` to
+9 -12
View File
@@ -52,21 +52,16 @@ interface ComposedEntry {
}
interface BootComposition {
bundlePatchPaths(packageDir: string, bundle: { patch: string | string[] }): string[]
loadOverlayPatches(binName: string, file: string): unknown[]
composeEntries(layers: readonly unknown[][]): ComposedEntry[]
}
const REPO_ROOT = process.cwd()
const BUNDLE_LAYERS = [
{
manifest: join(REPO_ROOT, 'packages/bundle/base/package.json'),
patch: join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml'),
},
{
manifest: join(REPO_ROOT, 'packages/bundle/web-app/package.json'),
patch: join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml'),
},
] as const
const BUNDLE_LAYERS = ['packages/bundle/base', 'packages/bundle/web-app'].map(dir => ({
dir: join(REPO_ROOT, dir),
manifest: join(REPO_ROOT, dir, 'package.json'),
}))
const bundleResolvers = BUNDLE_LAYERS.map(layer => createRequire(layer.manifest))
const webBundleResolver = bundleResolvers[1]
if (webBundleResolver === undefined) throw new Error('assembled boot: web bundle resolver missing')
@@ -97,8 +92,10 @@ const comboReference = (ids: readonly string[], rev: string): string =>
/** Derive the assembled browser graph from the same bundle patches and package declarations as `dsh web`. */
function loadAssembledPlugins(): readonly AssembledPlugin[] {
const entries = appBoot.composeEntries(BUNDLE_LAYERS.map(layer =>
appBoot.loadOverlayPatches('assembled boot', layer.patch)))
const entries = appBoot.composeEntries(BUNDLE_LAYERS.map((layer) => {
const declared = (JSON.parse(readFileSync(layer.manifest, 'utf8')) as { dsh: { bundle: { patch: string | string[] } } }).dsh.bundle
return appBoot.bundlePatchPaths(layer.dir, declared).flatMap(patch => appBoot.loadOverlayPatches('assembled boot', patch))
}))
const plugins = new Map<string, AssembledPlugin>()
for (const entry of entries) {
if (entry.disabled === true || typeof entry.name !== 'string') continue
+1 -2
View File
@@ -153,9 +153,8 @@ export function createAssembledRemote(options: AssembledRemoteOptions = {}): Ass
'session/modelCatalog': structuredClone(fixture.modelCatalog),
'agentPresets/list': structuredClone(fixture.agentPresets),
'commands/list': structuredClone(fixture.commands),
'settings/canOpenAgentPresetDirectory': ok(true),
'settings/openSettingsDocument': ok({ opened: true }),
'settings/openAgentPresetDirectory': ok({ opened: true }),
'subagents/list': ok({ entries: [], parentAvailable: true }),
'terminal/list': ok([]),
'skills/list': ok({ skills: [] }),
'session/canOpenWorkspacePath': ok(true),
@@ -0,0 +1,6 @@
- dialog "创建和修改预设":
- heading "创建和修改预设" [level=2]
- button "关闭"
- paragraph: 预设由已安装的 bundle 声明,本页只负责选择。要创建或修改预设,请用「创造模式」开始一个任务:Agent 会编写一个声明或覆盖该预设的 bundle,并安装到当前 profile。
- button "关闭"
- button "让 Agent 帮我创建预设模式"
@@ -1,13 +0,0 @@
- dialog "复制预设 · 复制自 极简模式":
- heading "复制预设 · 复制自 极简模式" [level=2]
- button "关闭"
- paragraph: 复制后会在本机保存一份预设,你可以打开目录修改配置。标识符用作目录名,创建后不能修改。
- text: 标识符
- textbox "标识符":
- /placeholder: my-agent
- text: 名称
- textbox "名称":
- /placeholder: 在模式列表中显示的名称,不填则使用标识符
- alert: 请填写标识符。
- button "取消"
- button "创建" [disabled]
@@ -1,66 +0,0 @@
- dialog "设置":
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
- button "Agent 预设"
- button "打开配置文件"
- button "关闭"
- heading "Agent 预设" [level=2]
- paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。
- text: 新任务可选择模式 Beta
- paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。
- switch "新任务可选择模式" [checked]
- heading "内置" [level=3]
- list:
- listitem:
- 'button "新任务默认: 标准模式" [disabled] [pressed]':
- text: 标准模式 新任务默认
- code: standard
- text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。
- 'button "模式说明: 标准模式"': 模式说明
- 'button "如何使用: 标准模式"': 如何使用
- 'button "查看配置: 标准模式"': 查看配置
- 'button "复制预设: 标准模式"': 复制预设
- listitem:
- 'button "设为新任务默认: PTC 模式"':
- text: PTC 模式 内置
- code: ptc
- text: 包含标准模式的所有能力,更适合批量调用工具,并对结果进行筛选、整理、去重、统计或汇总的任务。
- 'button "模式说明: PTC 模式"': 模式说明
- 'button "如何使用: PTC 模式"': 如何使用
- 'button "查看配置: PTC 模式"': 查看配置
- 'button "复制预设: PTC 模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 极简模式"':
- text: 极简模式 内置
- code: minimal
- text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。
- 'button "模式说明: 极简模式"': 模式说明
- 'button "如何使用: 极简模式"': 如何使用
- 'button "查看配置: 极简模式"': 查看配置
- 'button "复制预设: 极简模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 创造模式"':
- text: 创造模式 内置
- code: cordis
- text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。
- 'button "模式说明: 创造模式"': 模式说明
- 'button "如何使用: 创造模式"': 如何使用
- 'button "查看配置: 创造模式"': 查看配置
- 'button "复制预设: 创造模式"': 复制预设
- heading "自定义" [level=3]
- list:
- listitem:
- 'button "设为新任务默认: 我的模式"':
- text: 我的模式 自定义
- code: my-agent
- text: 仅提供持久 shell 的单工具编码 Agent。
- 'button "查看路径: 我的模式"': 查看路径
- 'button "复制预设: 我的模式"': 复制预设
- 'button "删除: 我的模式"': 删除
- paragraph:
- text: 预设文件:
- code: {{presetRoot}}/my-agent
- button "让 Agent 帮我创建预设模式"
@@ -1,73 +0,0 @@
- dialog "设置":
- navigation:
- text: 设置
- button "通用设置"
- button "模型"
- button "内置插件"
- button "Agent 预设"
- button "打开配置文件"
- button "关闭"
- heading "Agent 预设" [level=2]
- paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。
- text: 新任务可选择模式 Beta
- paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。
- switch "新任务可选择模式" [checked]
- heading "内置" [level=3]
- list:
- listitem:
- 'button "新任务默认: 标准模式" [disabled] [pressed]':
- text: 标准模式 新任务默认
- code: standard
- text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。
- 'button "模式说明: 标准模式"': 模式说明
- 'button "如何使用: 标准模式"': 如何使用
- 'button "查看配置: 标准模式"': 查看配置
- 'button "复制预设: 标准模式"': 复制预设
- listitem:
- 'button "设为新任务默认: PTC 模式"':
- text: PTC 模式 内置
- code: ptc
- text: 包含标准模式的所有能力,更适合批量调用工具,并对结果进行筛选、整理、去重、统计或汇总的任务。
- 'button "模式说明: PTC 模式"': 模式说明
- 'button "如何使用: PTC 模式"': 如何使用
- 'button "查看配置: PTC 模式"': 查看配置
- 'button "复制预设: PTC 模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 极简模式"':
- text: 极简模式 内置
- code: minimal
- text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。
- 'button "模式说明: 极简模式"': 模式说明
- 'button "如何使用: 极简模式"': 如何使用
- 'button "查看配置: 极简模式"': 查看配置
- 'button "复制预设: 极简模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 创造模式"':
- text: 创造模式 内置
- code: cordis
- text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。
- 'button "模式说明: 创造模式"': 模式说明
- 'button "如何使用: 创造模式"': 如何使用
- 'button "查看配置: 创造模式"': 查看配置
- 'button "复制预设: 创造模式"': 复制预设
- heading "自定义" [level=3]
- list:
- listitem:
- 'button "加载失败: broken-yaml" [disabled]':
- text: broken-yaml 加载失败 自定义
- code: broken-yaml
- text: 暂无描述。
- alert: "the composition is not valid YAML: unexpected end of the stream within a flow collection (3:1)"
- 'button "查看路径: broken-yaml"': 查看路径
- 'button "复制预设: broken-yaml" [disabled]': 预设加载失败,不能复制
- 'button "删除: broken-yaml"': 删除
- listitem:
- 'button "加载失败: 幽灵预设" [disabled]':
- text: 幽灵预设 加载失败 自定义
- code: ghost
- text: composition 已被手动删除。
- alert: the composition file agent.cordis.yml is missing — the directory still occupies the id; delete it or restore the file
- 'button "查看路径: 幽灵预设"': 查看路径
- 'button "复制预设: 幽灵预设" [disabled]': 预设加载失败,不能复制
- 'button "删除: 幽灵预设"': 删除
- button "让 Agent 帮我创建预设模式"
@@ -8,6 +8,7 @@
- button "打开配置文件"
- button "关闭"
- heading "Agent 预设" [level=2]
- button "如何创建或修改预设"
- paragraph: 选择 Agent 的工具和工作方式。日常任务用「标准模式」,扩展 DSH 的能力用「创造模式」。
- text: 新任务可选择模式 Beta
- paragraph: 开启后,可为每个新任务选择模式,并在这里设置默认值。关闭后,新任务使用应用配置的默认预设。已有任务不受影响。
@@ -21,8 +22,6 @@
- text: 处理代码、文件和资料,适合大多数任务。Agent 会按需使用检索、编辑和终端等工具。
- 'button "模式说明: 标准模式"': 模式说明
- 'button "如何使用: 标准模式"': 如何使用
- 'button "查看配置: 标准模式"': 查看配置
- 'button "复制预设: 标准模式"': 复制预设
- listitem:
- 'button "设为新任务默认: PTC 模式"':
- text: PTC 模式 内置
@@ -30,8 +29,6 @@
- text: 包含标准模式的所有能力,更适合批量调用工具,并对结果进行筛选、整理、去重、统计或汇总的任务。
- 'button "模式说明: PTC 模式"': 模式说明
- 'button "如何使用: PTC 模式"': 如何使用
- 'button "查看配置: PTC 模式"': 查看配置
- 'button "复制预设: PTC 模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 极简模式"':
- text: 极简模式 内置
@@ -39,8 +36,6 @@
- text: Agent 仅使用终端工具完成任务,适合测试和对比其基础表现。
- 'button "模式说明: 极简模式"': 模式说明
- 'button "如何使用: 极简模式"': 如何使用
- 'button "查看配置: 极简模式"': 查看配置
- 'button "复制预设: 极简模式"': 复制预设
- listitem:
- 'button "设为新任务默认: 创造模式"':
- text: 创造模式 内置
@@ -48,7 +43,3 @@
- text: 用对话定制 DSH:让 Agent 编写插件,添加新功能或界面;也能组合工具和提示词,创建自己的模式。
- 'button "模式说明: 创造模式"': 模式说明
- 'button "如何使用: 创造模式"': 如何使用
- 'button "查看配置: 创造模式"': 查看配置
- 'button "复制预设: 创造模式"': 复制预设
- heading "自定义" [level=3]
- button "让 Agent 帮我创建预设模式"
@@ -3,4 +3,3 @@
- menuitem "PTC mode Includes all Standard mode capabilities. Better suited to tasks that call tools in batches and then filter, organize, deduplicate, count, or summarize the results."
- menuitem "Minimal mode The agent works using only a terminal tool. Useful for testing and comparing its basic performance."
- menuitem "Creator mode Customize DSH through conversation. Let the agent write plugins that add features or UI, or combine tools and prompts to create your own mode."
- menuitem "Refusing mode Resolves, then refuses to start."
@@ -1 +1 @@
- alert: "新建会话失败:agent-preset/invalid: agent-presets: preset \"renamed-plugin\" failed to mount: row \"ghost\" names a plugin that cannot be resolved: @deepseek-ai/dsh-no-such-plugin"
- alert: "新建会话失败:agent-preset/invalid: ghost (@deepseek-ai/dsh-no-such-plugin): never started"
-1
View File
@@ -182,7 +182,6 @@
"isDefault": false
}
],
"authorable": true,
"modeSelectionEnabled": true
}
},
+1 -1
View File
@@ -8,7 +8,7 @@ import type { AgentHandle } from '@deepseek-ai/dsh-agent'
import { ToolCallId, createUserMessage } from '@deepseek-ai/dsh-llm'
import { SessionId } from '@deepseek-ai/dsh-session'
import type { Session } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import type {} from '@deepseek-ai/dsh-system-prompt'
import {
assertFixtureInventory,
+7 -19
View File
@@ -1,7 +1,4 @@
/** An explicit New Session the Host refuses reports through the Workspace notice in the shipped Web composition. */
import { mkdir, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright'
import { describe, expect, it, onTestFinished } from 'vitest'
@@ -13,28 +10,19 @@ const MODE = webSnapshotMode()
/** The default preset of this lane: its one row names a package the Host cannot resolve, as a preset copied before a plugin rename does. */
const PRESET_ID = 'renamed-plugin'
/**
* Seed a user preset root holding one preset the roster reads as broken.
*
* Discovery reports the unresolvable row before any mount, so the refusal
* carries no temp path and the notice text is stable across runs.
* @param root - the lane-owned preset root.
*/
async function seedBrokenPreset(root: string): Promise<void> {
const directory = join(root, PRESET_ID)
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'agent.cordis.yml'), '- id: ghost\n name: \'@deepseek-ai/dsh-no-such-plugin\'\n')
await writeFile(join(directory, 'preset.yml'), 'name: Renamed plugin\ndescription: Names a plugin that no longer resolves.\n')
/** The default preset of this lane as a declaration: its one row names a package the Host cannot resolve. */
const BROKEN_PRESET = {
id: PRESET_ID,
name: 'Renamed plugin',
description: 'Names a plugin that no longer resolves.',
plugins: [{ id: 'ghost', name: '@deepseek-ai/dsh-no-such-plugin' }],
}
describe.skipIf(MODE === 'record')('web e2e: refused New Session', () => {
it('shows the Host refusal as a notice and leaves the Workspace without a Session', async () => {
const presetRoot = await realpath(await mkdtemp(join(tmpdir(), 'dsh-web-e2e-broken-preset-')))
onTestFinished(() => rm(presetRoot, { recursive: true, force: true }))
await seedBrokenPreset(presetRoot)
const scaffold = await launchWebScaffold({
firstUse: true,
agentPresets: { roots: [{ path: presetRoot, trust: 'user' }], default: PRESET_ID },
agentPresets: { default: PRESET_ID, definitions: [BROKEN_PRESET] },
})
onTestFinished(() => scaffold.close())
const browser = await chromium.launch()
+1 -1
View File
@@ -56,7 +56,7 @@ fs.appendFileSync(${JSON.stringify(openLog)}, JSON.stringify({ path, action, con
scaffold = await launchWebScaffold({
openInAppEnvironment: createLaunchEnvironmentSnapshot([{ source: 'process', values: { SSH_CONNECTION: '10.0.0.2 55000 10.0.0.9 22' } }]),
extraOverlayPath: fileURLToPath(new URL('./present.overlay.yml', import.meta.url)),
agentPresets: { roots: [], default: 'ptc' }, compareReplaySession: true,
agentPresets: { default: 'ptc' }, compareReplaySession: true,
...(MODE === 'record' ? {} : { replayFixture: FIXTURE }),
})
// File associations belong to the desktop rather than the recorded Session.
+1 -1
View File
@@ -8,7 +8,7 @@ import { describe, expect, it } from 'vitest'
import { SESSION_FORMAT_VERSION, SessionId } from '@deepseek-ai/dsh-session'
import { generationLogPath } from '../../../packages/session/session-persistence-jsonl/src/format.ts'
import { scanZstdFrames } from '../../../packages/session/session-persistence-jsonl/src/zstd.ts'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import type {} from '@deepseek-ai/dsh-api-session-controller'
import { normalizeSessionSnapshots } from '@deepseek-ai/dsh-session-snapshot'
import { launchWebScaffold, webSnapshotMode } from './scaffold.ts'
+1 -1
View File
@@ -32,7 +32,7 @@ describe('web e2e: PTC program sandbox escalation', () => {
beforeAll(async () => {
scaffold = await launchWebScaffold({
agentPresets: { roots: [], default: 'ptc' },
agentPresets: { default: 'ptc' },
compareReplaySession: true,
...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }),
})
+1 -1
View File
@@ -32,7 +32,7 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
beforeAll(async () => {
scaffold = await launchWebScaffold({
agentPresets: { roots: [], default: 'ptc' },
agentPresets: { default: 'ptc' },
compareReplaySession: true,
...(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 }),
})
+1 -1
View File
@@ -4,7 +4,7 @@ import { join } from 'node:path'
import { expect, it } from 'vitest'
import type {} from '@deepseek-ai/dsh-skill'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import { launchWebScaffold, type WebScaffold } from './scaffold.ts'
async function writeSkill(root: string, name: string): Promise<void> {
+12 -30
View File
@@ -195,7 +195,8 @@ export function recordedSessionFixturePath(path: string, version: number): strin
/** The shipped composition under test: the dsh-base and dsh-web-app bundle patches over the empty profile root. */
const BASE_PATCH_PATH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
const WEB_PATCH_PATH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
const WEB_BUNDLE_DIR = join(REPO_ROOT, 'packages/bundle/web-app')
const WEB_BUNDLE_PATCH = (JSON.parse(readFileSync(join(WEB_BUNDLE_DIR, 'package.json'), 'utf8')) as { dsh: { bundle: { patch: string[] } } }).dsh.bundle
/** The installation anchor whose dependency surface the runtime resolution mirrors. */
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
@@ -405,18 +406,10 @@ export interface LaunchOptions {
/** Credential reference resolved by the shipped search provider. */
apiKeyEnv: string
}
/**
* Replace the roster row the scaffold pins by default (no configured roots,
* default `standard` — the plugin's own shipped presets). Supply this only
* to change WHICH presets a scenario sees beyond the shipped set — a
* writable user root, a different default. The patch lands after the
* default, so it wins.
*/
/** Preset selection default and additional declarative definitions for this scenario. */
agentPresets?: {
/** Roots to discover after the plugin's shipped root, in precedence order. */
roots: { path: string; trust: 'system' | 'user' }[]
/** The preset a session that names none is composed from. */
default: string
definitions?: import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition[]
}
/**
* Patch the telemetry exporter URL while preserving the shipped enabled
@@ -466,6 +459,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
const {
auditStartupEntries, composeEntries, createRuntimeResolution, initProfile,
mountRootInclude, readProfileManifest, readProfilePatches, loadProfileDirectory, loadOverlayPatches, PluginPackages,
bundlePatchPaths,
} = appBoot()
const mode = webSnapshotMode()
const replayFixture = options.replayFixture === undefined
@@ -554,7 +548,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// patch id that stops matching a row fails the boot sweep loudly instead of
// drifting).
const basePatches = loadOverlayPatches('web e2e scaffold', BASE_PATCH_PATH)
const surfacePatches = loadOverlayPatches('web e2e scaffold', WEB_PATCH_PATH)
const surfacePatches = bundlePatchPaths(WEB_BUNDLE_DIR, WEB_BUNDLE_PATCH).flatMap(file => loadOverlayPatches('web e2e scaffold', file))
const extraOverlayPatches = options.extraOverlayPath === undefined
? []
: (typeof options.extraOverlayPath === 'string' ? [options.extraOverlayPath] : options.extraOverlayPath)
@@ -573,17 +567,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
? []
: [{ id: 'agent-default-model', config: { provider: 'deepseek-official', model: 'deepseek-v4-flash' } }],
...extraOverlayPatches,
// The roster's shipped presets are the plugin's own, bundled inside
// `dsh-agent-presets` and prepended by it. Pin only the machine-local
// root away: a developer's own `~/.dsh/.agent-presets` must not be able
// to change a golden.
{
id: 'agent-presets',
config: {
default: 'standard',
includeUserRoot: false,
},
},
{ id: 'agent-preset-registry', config: { default: 'standard' } },
{ id: 'session-persistence-jsonl', config: { root: persistenceRoot } },
// Content search is enabled here although the shipped bundles default it
// off (`openAt: never`, pinned by apps/cli/tests/lazy-search-startup):
@@ -674,11 +658,10 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
// Open In scenario supplies launch facts that suppress every native probe.
{ id: 'open-in-app', disabled: options.openInAppEnvironment === undefined },
{ id: 'ui-open-in-app', disabled: options.openInAppEnvironment === undefined },
...options.agentPresets === undefined
? []
// Never the derived harness-home root: a developer's own presets must not
// be able to change a golden, whatever roots a scenario asks for.
: [{ id: 'agent-presets', config: { ...options.agentPresets, includeUserRoot: false } }],
...options.agentPresets === undefined ? [] : [
{ id: 'agent-preset-registry', config: { default: options.agentPresets.default } },
{ insert: (options.agentPresets.definitions ?? []).map(config => ({ id: `preset-${config.id}`, name: '@deepseek-ai/dsh-agent-preset', config })) },
],
...options.toolsMode === undefined ? [] : [{ id: 'tools', config: { mode: options.toolsMode } }],
...options.deepSeekSearch === undefined
? []
@@ -796,8 +779,7 @@ export async function launchWebScaffold(options: LaunchOptions = {}): Promise<We
ctx.loader.builtins.include = Include
// `cordis:group` beside it, exactly as `boot()` registers it: a group row is
// how a preset gives one `isolate` realm to a provider and its consumers,
// and a preset resolving package names from its own directory cannot reach
// `@deepseek-ai/cordis-plugin-group` by name.
// including when the deployment supplies only Cordis builtin resolution.
ctx.loader.builtins.group = Group
await ctx.loader.create({
name: 'cordis:include',
+6 -4
View File
@@ -2,12 +2,13 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import type { Agent, AgentHandle } from '@deepseek-ai/dsh-agent'
import { composeEntries, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { bundlePatchPaths, composeEntries, loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import { ToolCallId, createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
import type { ContextFormed, GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm'
import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
@@ -67,7 +68,8 @@ const CATALOG_SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/sched
const CATALOG_FIXTURE = join(CATALOG_SNAPSHOT_DIR, 'session.v3.jsonl')
const CATALOG_EXPECTED = join(CATALOG_SNAPSHOT_DIR, 'catalog.expected.md')
const BASE_PATCH = fileURLToPath(new URL('../../../packages/bundle/base/cordis.patch.yml', import.meta.url))
const WEB_PATCH = fileURLToPath(new URL('../../../packages/bundle/web-app/cordis.patch.yml', import.meta.url))
const WEB_BUNDLE = fileURLToPath(new URL('../../../packages/bundle/web-app/', import.meta.url))
const WEB_PATCHES = bundlePatchPaths(WEB_BUNDLE, (JSON.parse(readFileSync(join(WEB_BUNDLE, 'package.json'), 'utf8')) as { dsh: { bundle: { patch: string[] } } }).dsh.bundle)
const CATALOG_NOW = Date.parse('2099-08-25T12:00:00.000Z')
const CATALOG_SESSION_ID = SessionId('schedule-catalog-web-e2e')
const CATALOG_TITLE = 'Active schedule catalog'
@@ -667,11 +669,11 @@ describe.skipIf(MODE === 'record')('web e2e: active Schedule catalog', () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-schedule-catalog'))
const base = composeEntries([
loadOverlayPatches('Schedule catalog base roster', BASE_PATCH),
loadOverlayPatches('Schedule catalog base roster', WEB_PATCH),
...WEB_PATCHES.map(file => loadOverlayPatches('Schedule catalog base roster', file)),
])
const scheduled = composeEntries([
loadOverlayPatches('Schedule catalog overlay roster', BASE_PATCH),
loadOverlayPatches('Schedule catalog overlay roster', WEB_PATCH),
...WEB_PATCHES.map(file => loadOverlayPatches('Schedule catalog overlay roster', file)),
loadOverlayPatches('Schedule catalog overlay roster', OVERLAY),
])
expect(base.find(entry => entry.id === 'ui-schedule')).toMatchObject({
+3 -5
View File
@@ -295,11 +295,9 @@ describe('web e2e: seeded history renders through cold resume', () => {
// The seed carries a session/title event: the title unit is host-plane, so
// it folds the detached log and serves the value with nothing composed.
expect(typeof projections.values.title).toBe('string')
// `todos` is absent because its unit belongs to the agent preset and this
// directly seeded session never composed that preset. History computes
// the baseline through the standard projection registry without mounting
// an Agent composition as a read side effect.
expect(projections.values).not.toHaveProperty('todos')
// Eager preset activation registers the todo projection before any Agent
// opens this log. A log without todo events projects its empty value.
expect(projections.values.todos).toBeNull()
// The session-stats unit is a shipped web-app bundle row: whole-log
// turn/step counts ride the same tail block (the stats strip's source).
const sessionStats = projections.values.sessionStats as { turns: number; steps: number } | undefined
+1 -1
View File
@@ -717,7 +717,7 @@ describe('web e2e: settings modal and General preferences', () => {
await dialog.waitFor({ timeout: 10_000 })
await dialog.getByRole('button', { name: 'English' }).waitFor({ timeout: 10_000 })
// The plugin list resolves shipped preset names through the en
// dictionaries instead of echoing the preset files' Chinese metadata.
// dictionaries instead of echoing the preset declarations' Chinese metadata.
await dialog.getByRole('button', { name: 'Built-in plugins', exact: true }).click()
const presetSwitcher = dialog.getByRole('button', { name: 'Choose the agent preset to inspect' })
await presetSwitcher.waitFor({ timeout: 10_000 })
+1 -1
View File
@@ -19,7 +19,7 @@ import { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
import type {} from '@deepseek-ai/dsh-sandbox-policy'
import type {} from '@deepseek-ai/dsh-user-approval'
import type {} from '@deepseek-ai/dsh-permission-presets'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import type {} from '@deepseek-ai/dsh-commands'
import type {} from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-terminal'
+1 -1
View File
@@ -66,7 +66,7 @@ describe.skipIf(MODE === 'record')('web e2e: dedicated Skill tool row', () => {
const output = call.locator('pre')
await output.waitFor()
expect(await output.textContent()).toContain('<skill_content name="editing-cordis-compositions">')
expect(await output.textContent()).toContain('Each Bundle registers its dormant default provider and exclusively uses its pinned package-local platform CLI')
expect(await output.textContent()).toContain('Agent presets are ordinary `@deepseek-ai/dsh-agent-preset` declarations carried by bundle patches.')
expect(await output.evaluate(element => getComputedStyle(element.parentElement!).maxHeight)).toBe('260px')
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
+1 -1
View File
@@ -14,7 +14,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
"@deepseek-ai/dsh-agent-preset-registry": "workspace:^",
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
"@deepseek-ai/dsh-client-store": "workspace:^",
"@deepseek-ai/dsh-client-ui-chat": "workspace:^",
@@ -5,7 +5,7 @@ import { scheduler } from 'node:timers/promises'
import { Context } from '@deepseek-ai/cordis'
import AgentLoop, { turnBoundaryProjectionDefinition } from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
import SessionStore, {
interruptedTurnClosers,
SessionId,
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/capability-seams.md
capability-seams.md: bbd44bce011404ee476131b9f498618a8ef5c708
capability-seams.zh.md: 4e9f826bc27420ba372ac6feb688cc23bc58e141
capability-seams.md: 66425afe98bcbefe69912bf130cb72bba9435ec0
capability-seams.zh.md: faa31be64081a80d58a0f302f91c737c34ed3f38
+3 -3
View File
@@ -143,7 +143,7 @@ flowchart LR
svc_userQuestions["ctx.userQuestions<br/>Human question/answer seam"]
pkg_plan_mode["plan-mode"]
svc_planMode["ctx.planMode<br/>Plan collaboration state"]
pkg_agent_presets["agent-presets"]
pkg_agent_preset_registry["agent-preset-registry"]
svc_agentPresets["ctx.agentPresets<br/>Per-session agent composition"]
pkg_commands["commands"]
svc_commands["ctx.commands<br/>Human command registry"]
@@ -264,7 +264,7 @@ flowchart LR
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_agent_preset_registry --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_api_job_controller --> svc_jobController
pkg_api_session_controller --> svc_sessionController
@@ -600,7 +600,7 @@ flowchart LR
| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`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 PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation. |
| `ctx.userQuestions` | `seam` | [`user-questions`](../packages/interaction/user-questions) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI front ends 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.agentPresets` | `core` | [`agent-presets`](../packages/preset/agent-presets) | - | - | - | Discovers preset directories over trusted and user-authored roots and mounts one preset cordis.yml under an agent scope during creation, rejecting a row that never activates or that publishes into the root service realm. |
| `ctx.agentPresets` | `core` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | - | - | - | Eagerly mounts YAML-declared preset revisions, binds Agents and cold readers to scoped contributions, and retains retired revisions until their last user releases them. |
| `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) | - | [`api-session-controller`](../packages/api/session-controller), [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title) | - | Domains register state-driven fold units; the eager drive keeps per-session watermark states and the Session controller serves baselines and pushes changed values. |
| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`api-session-controller`](../packages/api/session-controller), [`session-query`](../packages/session-query/session-query), [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent) | - | 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. |
+3 -3
View File
@@ -145,7 +145,7 @@ flowchart LR
svc_userQuestions["ctx.userQuestions<br/>Human question/answer seam"]
pkg_plan_mode["plan-mode"]
svc_planMode["ctx.planMode<br/>Plan collaboration state"]
pkg_agent_presets["agent-presets"]
pkg_agent_preset_registry["agent-preset-registry"]
svc_agentPresets["ctx.agentPresets<br/>Per-session agent composition"]
pkg_commands["commands"]
svc_commands["ctx.commands<br/>Human command registry"]
@@ -266,7 +266,7 @@ flowchart LR
pkg_agent --> svc_agents
pkg_agent_default_model --> svc_agentDefaultModel
pkg_agent_loop --> svc_agentLoop
pkg_agent_presets --> svc_agentPresets
pkg_agent_preset_registry --> svc_agentPresets
pkg_api_gateway --> svc_typertGateway
pkg_api_job_controller --> svc_jobController
pkg_api_session_controller --> svc_sessionController
@@ -602,7 +602,7 @@ flowchart LR
| `ctx.tools` | `core` | [`tools`](../packages/core/tools) | - | [`agent-loop`](../packages/core/agent-loop), [`tool-ask-user`](../packages/interaction/tool-ask-user), [`tool-bash`](../packages/shell/tool-bash), [`tool-cordis`](../packages/extensions/tool-cordis), [`tool-fs`](../packages/fs/tool-fs), [`tool-terminal`](../packages/terminal/tool-terminal), [`tool-skill`](../packages/skill/tool-skill), [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-todo`](../packages/todo/tool-todo), [`tool-web`](../packages/web/tool-web) | - | 注册能力,负责 PTC mode 传输,并让调用依次经过策略前处理、单调守卫、环绕分派、策略后处理和最终结果观测。 |
| `ctx.userQuestions` | `seam` | [`user-questions`](../packages/interaction/user-questions) | - | [`tool-ask-user`](../packages/interaction/tool-ask-user) | - | UI 前端提供当前生效的人工回答提供方;tool-ask-user 在提供方无关的 ask() promise 上暂停工具调用。 |
| `ctx.planMode` | `core` | [`plan-mode`](../packages/plan/plan-mode) | - | - | - | 折叠已记录的计划/模式状态,在轮次边界刷新用户选择,渲染由部署方拥有的指导信息,注册 /plan,并在状态转换期间保持计划退出 schema 稳定。 |
| `ctx.agentPresets` | `core` | [`agent-presets`](../packages/preset/agent-presets) | - | - | - | 在受信任根目录与用户创作根目录上发现 preset 目录,并在创建期把一份 preset cordis.yml 挂载到 agent 作用域之下,拒绝始终未激活或向根服务 realm 发布服务的行。 |
| `ctx.agentPresets` | `core` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) | - | - | - | 立即挂载 YAML 声明的 preset 版本,把 Agent 和冷读取绑定到作用域内的贡献,并保留已替换的版本,直到最后一个使用者释放它。 |
| `ctx.commands` | `core` | [`commands`](../packages/interaction/commands) | - | - | - | 插件注册直接面向人的命令,而不会把调用发送给模型。 |
| `ctx.sessionProjections` | `core` | [`session-projection`](../packages/session/session-projection) | - | [`api-session-controller`](../packages/api/session-controller), [`tool-todo`](../packages/todo/tool-todo), [`session-title`](../packages/session/session-title) | - | 各领域注册由状态驱动的折叠单元;主动驱动过程维护每个会话的水位状态,Session controller 提供 baseline 并推送发生变化的值。 |
| `ctx.sessionProjectionCache` | `core` | [`session-projection-cache`](../packages/session/session-projection-cache) | - | [`api-session-controller`](../packages/api/session-controller), [`session-query`](../packages/session-query/session-query), [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent) | - | 按会话持久保存投影单元状态的检查点(节流检查点,以及轮次/结束/分离时的必选检查点),并提供冷读取阶梯:缓存行加持久化尾部回放,因此列表读取永远不需要加载完整日志。 |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: d3d1b4fe47bf6dc745dc9a00ac6ef317ab0d3ca8
config-catalog.zh.md: 3f204af30d5e973c03cd24c6c2873bb7756d334e
config-catalog.md: 4ce0bc0a4f660fe3f27091012072c5799231f12b
config-catalog.zh.md: b8077cd13fff1f7c3b0a89f9f9c335a8716c18ac
+25 -40
View File
@@ -113,51 +113,36 @@ Depends on: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/cor
Source: [`packages/core/agent-loop/src/index.ts:319`](../packages/core/agent-loop/src/index.ts)
<a id="deepseek-aidsh-agent-presets"></a>
<a id="deepseek-aidsh-agent-preset"></a>
## `@deepseek-ai/dsh-agent-presets`
## `@deepseek-ai/dsh-agent-preset`
Requires: `agentPresets`
```ts config-catalog
/** Definition submitted to the preset registry. */
export type Config = PresetDefinition
```
Depends on: [`PresetDefinition`](../packages/preset/agent-preset-registry/src/index.ts)
Source: [`packages/preset/agent-preset/src/index.ts:9`](../packages/preset/agent-preset/src/index.ts)
<a id="deepseek-aidsh-agent-preset-registry"></a>
## `@deepseek-ai/dsh-agent-preset-registry`
Requires: `loader` · `sessionProjections`
```ts config-catalog
/** Plugin config: which preset is the default, and where presets live. */
/** Registry selection policy. */
export interface Config {
/** Preset id mounted when a caller names none. Missing at mount time fails loud. */
/** Deployment default when the caller omits a preset. */
default: string
/** Scanned roots in precedence order; an earlier root wins a duplicate id. */
roots: PresetRoot[]
/**
* Prepend this package's bundled shipped presets as a `system` root, before
* every configured root, so the shipped set always mounts and wins a
* duplicate id. The default survives a whole-`config` patch replacement;
* only an explicit `false` — a deployment supplying purely its own presets,
* or an embedder using the roster as bare machinery — drops the set.
*/
includeShippedRoot: boolean
/**
* Append the harness home's `USER_PRESET_DIR` as a `user` root, after every
* configured root. False mounts a roster without the derived writable root.
*/
includeUserRoot: boolean
}
/** One directory scanned for preset subdirectories. */
export interface PresetRoot {
/** Directory holding one subdirectory per preset; a leading `~` expands. */
path: string
/** Trust recorded on every preset discovered under this root. */
trust: PresetTrust
}
/**
* Where a preset's composition came from. A `system` preset ships with the
* deployment; a `user` preset was authored locally, by a person or by an
* agent, and therefore carries the same trust as shell access.
*/
export type PresetTrust = 'system' | 'user'
```
Source: [`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts)
Source: [`packages/preset/agent-preset-registry/src/preset.ts:12`](../packages/preset/agent-preset-registry/src/preset.ts)
<a id="deepseek-aidsh-agent-tool-presentation"></a>
@@ -240,14 +225,14 @@ Source: [`packages/api/session-controller/src/index.ts:78`](../packages/api/sess
## `@deepseek-ai/dsh-api-settings-controller`
```ts config-catalog
/** Native document-opening policy. */
export interface Config {
/** Override platform desktop-opener detection. */
readonly nativeOpen?: boolean
/** Host integrations replaceable by direct unit tests. */
export interface SettingsControllerInternals {
/** Host text-editor integration used to open the settings document. */
readonly openTextFile?: (path: string, signal: AbortSignal) => Promise<void>
}
```
Source: [`packages/api/settings-controller/src/index.ts:36`](../packages/api/settings-controller/src/index.ts)
Source: [`packages/api/settings-controller/src/index.ts:35`](../packages/api/settings-controller/src/index.ts)
<a id="deepseek-aidsh-api-terminal-controller"></a>
+29 -44
View File
@@ -115,51 +115,36 @@ Depends on: [`AgentOptions`](subsystems/core.zh.md) · [`SessionId`](subsystems/
来源:[`packages/core/agent-loop/src/index.ts:319`](../packages/core/agent-loop/src/index.ts)
<a id="deepseek-aidsh-agent-presets"></a>
<a id="deepseek-aidsh-agent-preset"></a>
## `@deepseek-ai/dsh-agent-presets`
## `@deepseek-ai/dsh-agent-preset`
需要:`loader` · `sessionProjections`
需要: `agentPresets`
```ts config-catalog
/** Plugin config: which preset is the default, and where presets live. */
export interface Config {
/** Preset id mounted when a caller names none. Missing at mount time fails loud. */
default: string
/** Scanned roots in precedence order; an earlier root wins a duplicate id. */
roots: PresetRoot[]
/**
* Prepend this package's bundled shipped presets as a `system` root, before
* every configured root, so the shipped set always mounts and wins a
* duplicate id. The default survives a whole-`config` patch replacement;
* only an explicit `false` — a deployment supplying purely its own presets,
* or an embedder using the roster as bare machinery — drops the set.
*/
includeShippedRoot: boolean
/**
* Append the harness home's `USER_PRESET_DIR` as a `user` root, after every
* configured root. False mounts a roster without the derived writable root.
*/
includeUserRoot: boolean
}
/** One directory scanned for preset subdirectories. */
export interface PresetRoot {
/** Directory holding one subdirectory per preset; a leading `~` expands. */
path: string
/** Trust recorded on every preset discovered under this root. */
trust: PresetTrust
}
/**
* Where a preset's composition came from. A `system` preset ships with the
* deployment; a `user` preset was authored locally, by a person or by an
* agent, and therefore carries the same trust as shell access.
*/
export type PresetTrust = 'system' | 'user'
/** Definition submitted to the preset registry. */
export type Config = PresetDefinition
```
来源:[`packages/preset/agent-presets/src/preset.ts:52`](../packages/preset/agent-presets/src/preset.ts)
依赖: [`PresetDefinition`](../packages/preset/agent-preset-registry/src/index.ts)
来源: [`packages/preset/agent-preset/src/index.ts:9`](../packages/preset/agent-preset/src/index.ts)
<a id="deepseek-aidsh-agent-preset-registry"></a>
## `@deepseek-ai/dsh-agent-preset-registry`
需要: `loader` · `sessionProjections`
```ts config-catalog
/** Registry selection policy. */
export interface Config {
/** Deployment default when the caller omits a preset. */
default: string
}
```
来源: [`packages/preset/agent-preset-registry/src/preset.ts:12`](../packages/preset/agent-preset-registry/src/preset.ts)
<a id="deepseek-aidsh-agent-tool-presentation"></a>
@@ -244,14 +229,14 @@ export interface Config {
## `@deepseek-ai/dsh-api-settings-controller`
```ts config-catalog
/** Native document-opening policy. */
export interface Config {
/** Override platform desktop-opener detection. */
readonly nativeOpen?: boolean
/** Host integrations replaceable by direct unit tests. */
export interface SettingsControllerInternals {
/** Host text-editor integration used to open the settings document. */
readonly openTextFile?: (path: string, signal: AbortSignal) => Promise<void>
}
```
来源:[`packages/api/settings-controller/src/index.ts:36`](../packages/api/settings-controller/src/index.ts)
来源: [`packages/api/settings-controller/src/index.ts:35`](../packages/api/settings-controller/src/index.ts)
<a id="deepseek-aidsh-api-terminal-controller"></a>
+2 -2
View File
@@ -529,11 +529,11 @@
]
},
{
"name": "@deepseek-ai/dsh-agent-presets",
"name": "@deepseek-ai/dsh-agent-preset-registry",
"version": "0.1.5-rc.2",
"installations": [
{
"location": "node_modules/@deepseek-ai/dsh-agent-presets",
"location": "node_modules/@deepseek-ai/dsh-agent-preset-registry",
"direct": false,
"optional": false,
"peer": false
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
event-producer-consumer.md: 2b373821a2d0c9acf2284c4195a928947700f1ce
event-producer-consumer.zh.md: 90ca2a4c76ac477265a60dad80c5f65d04509e90
event-producer-consumer.md: 741593e3f869994c260b580d9dddb18bdbf5193f
event-producer-consumer.zh.md: 323d37214254a54a9ca13d1918523cb1c92fd32a
+6 -6
View File
@@ -8,9 +8,9 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| Event | Mode | Declared in | Dispatchers | Listeners |
| --- | --- | --- | --- | --- |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:247`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:82`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:59`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
@@ -57,7 +57,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `session-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`) | - |
| `session/created` | `emit` | [`packages/core/session/src/index.ts:53`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:75`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`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), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:75`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`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), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) |
| `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:105`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `remotes` |
| `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:92`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
@@ -66,9 +66,9 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:147`](../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:153`](../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:164`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) |
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` |
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:176`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) |
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:153`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) |
@@ -89,7 +89,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| --- | --- | --- |
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
| `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` |
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` |
| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) |
| `internal/update` | - | [`app-boot`](../packages/boot/app-boot) |
| `slots/changed` | `ui-renderer` (`emit`) | - |
+14 -14
View File
@@ -10,9 +10,9 @@
| Event | Mode | Declared in | Dispatchers | Listeners |
| --- | --- | --- | --- | --- |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:247`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:82`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `remotes` |
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:59`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | `remotes` |
| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:363`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-presets`](../packages/preset/agent-presets), `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:261`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `agent-team`, `browser-use-runtime`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), [`schedule`](../packages/schedule/schedule), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:270`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:393`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), `session-controller`, [`session-telemetry`](../packages/session/session-telemetry) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs) |
@@ -23,11 +23,11 @@
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:353`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:280`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`agent`](../packages/core/agent), `agent-team`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`schedule`](../packages/schedule/schedule), `server`, `session-controller` |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:381`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:617`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:597`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:624`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:603`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:610`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:603`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:583`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:610`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:589`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:596`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:85`](../packages/interaction/user-approval/src/types.ts) | [`user-approval`](../packages/interaction/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp), `remotes` |
| `authorization/settled` | `emit` | [`packages/credentials/authorization/src/index.ts:57`](../packages/credentials/authorization/src/index.ts) | [`authorization`](../packages/credentials/authorization) (`events.dispatch`) | [`authorization`](../packages/credentials/authorization) |
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
@@ -43,9 +43,9 @@
| `credentials/reference-updated` | `emit` | [`packages/credentials/credentials/src/types.ts:90`](../packages/credentials/credentials/src/types.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | [`credentials`](../packages/credentials/credentials), `remotes` |
| `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`) | [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace), `workspace-controller` |
| `feedback/committed` | `parallel` | [`packages/feedback/message-feedback/src/index.ts:58`](../packages/feedback/message-feedback/src/index.ts) | [`message-feedback`](../packages/feedback/message-feedback) (`parallel`) | [`session-telemetry-otel`](../packages/session/session-telemetry-otel) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:66`](../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-observation-policy`](../packages/fs/fs-observation-policy) |
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:76`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` |
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:58`](../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-observation-policy`](../packages/fs/fs-observation-policy) |
| `fs/edit-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:67`](../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-observation-policy`](../packages/fs/fs-observation-policy) |
| `fs/observed` | `emit` | [`packages/fs/fs/src/index.ts:77`](../packages/fs/fs/src/index.ts) | [`tool-fs`](../packages/fs/tool-fs) (`emit`), [`tool-str-replace-editor`](../packages/fs/tool-str-replace-editor) (`emit`) | [`fs-observation-policy`](../packages/fs/fs-observation-policy), [`skill-filesystem`](../packages/skill/skill-filesystem), `workspace-files` |
| `fs/write-intent` | `waterfall` | [`packages/fs/fs/src/index.ts:59`](../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-observation-policy`](../packages/fs/fs-observation-policy) |
| `goal/activation-changed` | `emit` | [`packages/goal/goal/src/types.ts:150`](../packages/goal/goal/src/types.ts) | [`goal`](../packages/goal/goal) (`emit`) | `remotes` |
| `goal/changed` | `emit` | [`packages/goal/goal/src/domain.ts:114`](../packages/goal/goal/src/domain.ts) | [`goal`](../packages/goal/goal) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver) |
| `hmr/change` | `emit` | [`packages/boot/hmr/src/index.ts:30`](../packages/boot/hmr/src/index.ts) | [`hmr`](../packages/boot/hmr) (`emit`) | - |
@@ -59,7 +59,7 @@
| `session-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`) | - |
| `session/created` | `emit` | [`packages/core/session/src/index.ts:53`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`compaction`](../packages/compaction/compaction), [`goal`](../packages/goal/goal), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`schedule`](../packages/schedule/schedule), `server`, [`session`](../packages/core/session), `session-controller`, [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| `session/disposed` | `emit` | [`packages/core/session/src/index.ts:63`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), `agent-team`, `file-upload`, `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry), [`session-title`](../packages/session/session-title), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:75`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-presets`](../packages/preset/agent-presets), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`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), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/event` | `emit` | [`packages/core/session/src/index.ts:75`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`acp`](../packages/acp/acp), [`agent-instructions`](../packages/context/agent-instructions), [`agent-loop`](../packages/core/agent-loop), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `agent-team`, [`compaction`](../packages/compaction/compaction), [`compaction-basic`](../packages/compaction/compaction-basic), [`file-reference-local`](../packages/context/file-reference-local), `file-upload`, [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), [`hook-protocol`](../packages/hooks/hook-protocol), [`loader-smoke`](../packages/test-support/loader-smoke), `server`, [`session`](../packages/core/session), `session-controller`, [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`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), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval), [`workspace-changes`](../packages/deliverables/workspace-changes) |
| `session/flush` | `parallel` | [`packages/core/session/src/index.ts:84`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl), [`session-telemetry`](../packages/session/session-telemetry) |
| `settings/document-updated` | `emit` | [`packages/settings/settings/src/types.ts:105`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | `remotes` |
| `settings/updated` | `emit` | [`packages/settings/settings/src/types.ts:92`](../packages/settings/settings/src/types.ts) | [`settings`](../packages/settings/settings) (`events.dispatch`) | [`settings`](../packages/settings/settings) |
@@ -68,9 +68,9 @@
| `subagent/provider-added` | `emit` | [`packages/subagent/subagent/src/index.ts:147`](../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:153`](../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:164`](../packages/subagent/subagent/src/index.ts) | [`subagent`](../packages/subagent/subagent) (`events.dispatch`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`subagent`](../packages/subagent/subagent) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) |
| `system-prompt/assemble` | `waterfall` | [`packages/core/system-prompt/src/index.ts:31`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`waterfall`) | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), `browser-use-runtime`, [`session-reference`](../packages/context/session-reference), [`system-prompt`](../packages/core/system-prompt) |
| `system-prompt/change` | `emit` | [`packages/core/system-prompt/src/index.ts:37`](../packages/core/system-prompt/src/index.ts) | [`system-prompt`](../packages/core/system-prompt) (`emit`) | - |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/change` | `emit` | [`packages/core/tools/src/index.ts:208`](../packages/core/tools/src/index.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`), [`tools`](../packages/core/tools) (`emit`) | `browser-use-runtime`, [`tool-subagent`](../packages/subagent/tool-subagent) |
| `tools/execute` | `waterfall` | [`packages/core/tools/src/index.ts:164`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `browser-use-runtime`, `browser-use-stagehand-native`, `computer-use-cua-driver-native`, [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `timeout-policy` |
| `tools/post-execute` | `waterfall` | [`packages/core/tools/src/index.ts:176`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`spill-policy`](../packages/spill/spill-policy), [`tool-fs-search`](../packages/fs/tool-fs-search) |
| `tools/pre-execute` | `waterfall` | [`packages/core/tools/src/index.ts:153`](../packages/core/tools/src/index.ts) | [`tools`](../packages/core/tools) (`waterfall`) | `auto-review`, [`hooks-claude-code`](../packages/hooks/hooks-claude-code), [`hooks-codex`](../packages/hooks/hooks-codex), [`tool-jobs`](../packages/jobs/tool-jobs), [`workspace-changes`](../packages/deliverables/workspace-changes) |
@@ -91,7 +91,7 @@
| --- | --- | --- |
| `internal/dispatch` | - | `agent-team`, [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission-presets`](../packages/interaction/permission-presets), [`plan-mode`](../packages/plan/plan-mode), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`schedule`](../packages/schedule/schedule), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-log-deepseek`](../packages/session/session-log-deepseek), [`session-title`](../packages/session/session-title), [`subagent`](../packages/subagent/subagent), [`terminal-bash`](../packages/terminal/terminal-bash), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tool-workflow`](../packages/workflow/tool-workflow), [`tools`](../packages/core/tools), `ui-renderer`, [`user-approval`](../packages/interaction/user-approval), [`webhook`](../packages/webhook/webhook), [`workflow`](../packages/workflow/workflow) |
| `internal/plugin` | - | `computer-use-cua-driver-native`, `inspector`, `loader`, [`lsp-stdio`](../packages/lsp/lsp-stdio), [`mcp-client`](../packages/mcp/mcp-client), `modules` |
| `internal/service` | - | [`agent-presets`](../packages/preset/agent-presets), `gateway` |
| `internal/service` | - | [`agent-preset-registry`](../packages/preset/agent-preset-registry), `gateway` |
| `internal/status` | - | [`agent`](../packages/core/agent), `inspector`, [`web`](../packages/web/web) |
| `internal/update` | - | [`app-boot`](../packages/boot/app-boot) |
| `slots/changed` | `ui-renderer` (`emit`) | - |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/module-graph.md
module-graph.md: 8511f674c84bcfa11fb9e2b137b12025ec540c3e
module-graph.zh.md: 67ba8d7f6b2582200b8a2ecc2215f7b317880081
module-graph.md: 936324af096fa6b17e260e9d9cd6d03a34fb9bd7
module-graph.zh.md: c68f95c65af7df50dbfd61bf79d754c137c7f709
+26 -25
View File
@@ -300,7 +300,8 @@ flowchart TD
pkg_mcp_resources["mcp-resources"]
end
subgraph group_preset["packages/preset"]
pkg_agent_presets["agent-presets"]
pkg_agent_preset["agent-preset"]
pkg_agent_preset_registry["agent-preset-registry"]
pkg_persona["persona"]
end
subgraph group_ptc_runtime["packages/ptc-runtime"]
@@ -899,17 +900,15 @@ flowchart TD
pkg_mcp_resources --> pkg_scope
pkg_mcp_resources --> pkg_system_prompt
pkg_mcp_resources --> pkg_tools
pkg_agent_presets --> pkg_agent
pkg_agent_presets --> pkg_atomic_write
pkg_agent_presets --> pkg_home_paths
pkg_agent_presets --> pkg_invariants
pkg_agent_presets --> pkg_scope
pkg_agent_presets --> pkg_session
pkg_agent_presets --> pkg_session_projection
pkg_agent_presets --> pkg_settings
pkg_agent_presets --> pkg_system_prompt
pkg_agent_presets --> pkg_tools
pkg_agent_presets --> pkg_typert_protocol
pkg_agent_preset_registry --> pkg_agent
pkg_agent_preset_registry --> pkg_invariants
pkg_agent_preset_registry --> pkg_scope
pkg_agent_preset_registry --> pkg_session
pkg_agent_preset_registry --> pkg_session_projection
pkg_agent_preset_registry --> pkg_settings
pkg_agent_preset_registry --> pkg_system_prompt
pkg_agent_preset_registry --> pkg_tools
pkg_agent_preset_registry --> pkg_typert_protocol
pkg_schedule --> pkg_agent
pkg_schedule --> pkg_brand
pkg_schedule --> pkg_invariants
@@ -971,7 +970,7 @@ flowchart TD
pkg_tool_workflow --> pkg_tools
pkg_tool_workflow --> pkg_workflow
pkg_plugin_package_inventory_deepseek --> pkg_agent
pkg_plugin_package_inventory_deepseek --> pkg_agent_presets
pkg_plugin_package_inventory_deepseek --> pkg_agent_preset_registry
pkg_plugin_package_inventory_deepseek --> pkg_deepseek_llm_api_extensions
pkg_plugin_package_inventory_deepseek --> pkg_session
pkg_token_meter --> pkg_compaction
@@ -988,7 +987,7 @@ flowchart TD
pkg_session_query --> pkg_session_projection_cache
pkg_session_query --> pkg_session_title
pkg_session_query --> pkg_tool_todo
pkg_api_settings_controller --> pkg_agent_presets
pkg_api_settings_controller --> pkg_agent_preset_registry
pkg_api_settings_controller --> pkg_credentials
pkg_api_settings_controller --> pkg_native_command
pkg_api_settings_controller --> pkg_session
@@ -1005,7 +1004,7 @@ flowchart TD
pkg_tool_cordis --> pkg_agent
pkg_tool_cordis --> pkg_cordis_host_runner
pkg_tool_cordis --> pkg_tools
pkg_host_plugin_inventory --> pkg_agent_presets
pkg_host_plugin_inventory --> pkg_agent_preset_registry
pkg_host_plugin_inventory --> pkg_brand
pkg_host_plugin_inventory --> pkg_typert_protocol
pkg_mcp_client --> pkg_attachment
@@ -1016,6 +1015,7 @@ flowchart TD
pkg_mcp_client --> pkg_system_prompt
pkg_mcp_client --> pkg_timeout
pkg_mcp_client --> pkg_tools
pkg_agent_preset --> pkg_agent_preset_registry
pkg_session_telemetry_otel --> pkg_anonymous_user_id
pkg_session_telemetry_otel --> pkg_command_feedback
pkg_session_telemetry_otel --> pkg_llm
@@ -1060,7 +1060,7 @@ flowchart TD
pkg_agent_loop_testkit --> pkg_tools
pkg_webhook --> pkg_agent
pkg_webhook --> pkg_agent_default_model
pkg_webhook --> pkg_agent_presets
pkg_webhook --> pkg_agent_preset_registry
pkg_webhook --> pkg_invariants
pkg_webhook --> pkg_llm
pkg_webhook --> pkg_permission_presets
@@ -1068,7 +1068,7 @@ flowchart TD
pkg_webhook --> pkg_session_title
pkg_webhook --> pkg_workspace
pkg_subagent --> pkg_agent
pkg_subagent --> pkg_agent_presets
pkg_subagent --> pkg_agent_preset_registry
pkg_subagent --> pkg_attachment
pkg_subagent --> pkg_invariants
pkg_subagent --> pkg_jobs
@@ -1182,7 +1182,7 @@ flowchart TD
pkg_hooks_claude_code --> pkg_tools
pkg_api_session_controller --> pkg_agent
pkg_api_session_controller --> pkg_agent_default_model
pkg_api_session_controller --> pkg_agent_presets
pkg_api_session_controller --> pkg_agent_preset_registry
pkg_api_session_controller --> pkg_api_gateway
pkg_api_session_controller --> pkg_attachment
pkg_api_session_controller --> pkg_client_connection
@@ -1567,7 +1567,7 @@ flowchart TD
| [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
| [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
@@ -1579,15 +1579,16 @@ flowchart TD
| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) |
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`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-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) |
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-presets`](../packages/preset/agent-presets), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`tools`](../packages/core/tools) |
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-resources`](../packages/mcp/mcp-resources), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`agent-preset`](../packages/preset/agent-preset) | `preset` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) |
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
@@ -1595,8 +1596,8 @@ flowchart TD
| [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) |
| [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`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), [`session-query`](../packages/session-query/session-query), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`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), [`session-query`](../packages/session-query/session-query), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`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` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
@@ -1612,7 +1613,7 @@ flowchart TD
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`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), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`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), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
+26 -25
View File
@@ -302,7 +302,8 @@ flowchart TD
pkg_mcp_resources["mcp-resources"]
end
subgraph group_preset["packages/preset"]
pkg_agent_presets["agent-presets"]
pkg_agent_preset["agent-preset"]
pkg_agent_preset_registry["agent-preset-registry"]
pkg_persona["persona"]
end
subgraph group_ptc_runtime["packages/ptc-runtime"]
@@ -901,17 +902,15 @@ flowchart TD
pkg_mcp_resources --> pkg_scope
pkg_mcp_resources --> pkg_system_prompt
pkg_mcp_resources --> pkg_tools
pkg_agent_presets --> pkg_agent
pkg_agent_presets --> pkg_atomic_write
pkg_agent_presets --> pkg_home_paths
pkg_agent_presets --> pkg_invariants
pkg_agent_presets --> pkg_scope
pkg_agent_presets --> pkg_session
pkg_agent_presets --> pkg_session_projection
pkg_agent_presets --> pkg_settings
pkg_agent_presets --> pkg_system_prompt
pkg_agent_presets --> pkg_tools
pkg_agent_presets --> pkg_typert_protocol
pkg_agent_preset_registry --> pkg_agent
pkg_agent_preset_registry --> pkg_invariants
pkg_agent_preset_registry --> pkg_scope
pkg_agent_preset_registry --> pkg_session
pkg_agent_preset_registry --> pkg_session_projection
pkg_agent_preset_registry --> pkg_settings
pkg_agent_preset_registry --> pkg_system_prompt
pkg_agent_preset_registry --> pkg_tools
pkg_agent_preset_registry --> pkg_typert_protocol
pkg_schedule --> pkg_agent
pkg_schedule --> pkg_brand
pkg_schedule --> pkg_invariants
@@ -973,7 +972,7 @@ flowchart TD
pkg_tool_workflow --> pkg_tools
pkg_tool_workflow --> pkg_workflow
pkg_plugin_package_inventory_deepseek --> pkg_agent
pkg_plugin_package_inventory_deepseek --> pkg_agent_presets
pkg_plugin_package_inventory_deepseek --> pkg_agent_preset_registry
pkg_plugin_package_inventory_deepseek --> pkg_deepseek_llm_api_extensions
pkg_plugin_package_inventory_deepseek --> pkg_session
pkg_token_meter --> pkg_compaction
@@ -990,7 +989,7 @@ flowchart TD
pkg_session_query --> pkg_session_projection_cache
pkg_session_query --> pkg_session_title
pkg_session_query --> pkg_tool_todo
pkg_api_settings_controller --> pkg_agent_presets
pkg_api_settings_controller --> pkg_agent_preset_registry
pkg_api_settings_controller --> pkg_credentials
pkg_api_settings_controller --> pkg_native_command
pkg_api_settings_controller --> pkg_session
@@ -1007,7 +1006,7 @@ flowchart TD
pkg_tool_cordis --> pkg_agent
pkg_tool_cordis --> pkg_cordis_host_runner
pkg_tool_cordis --> pkg_tools
pkg_host_plugin_inventory --> pkg_agent_presets
pkg_host_plugin_inventory --> pkg_agent_preset_registry
pkg_host_plugin_inventory --> pkg_brand
pkg_host_plugin_inventory --> pkg_typert_protocol
pkg_mcp_client --> pkg_attachment
@@ -1018,6 +1017,7 @@ flowchart TD
pkg_mcp_client --> pkg_system_prompt
pkg_mcp_client --> pkg_timeout
pkg_mcp_client --> pkg_tools
pkg_agent_preset --> pkg_agent_preset_registry
pkg_session_telemetry_otel --> pkg_anonymous_user_id
pkg_session_telemetry_otel --> pkg_command_feedback
pkg_session_telemetry_otel --> pkg_llm
@@ -1062,7 +1062,7 @@ flowchart TD
pkg_agent_loop_testkit --> pkg_tools
pkg_webhook --> pkg_agent
pkg_webhook --> pkg_agent_default_model
pkg_webhook --> pkg_agent_presets
pkg_webhook --> pkg_agent_preset_registry
pkg_webhook --> pkg_invariants
pkg_webhook --> pkg_llm
pkg_webhook --> pkg_permission_presets
@@ -1070,7 +1070,7 @@ flowchart TD
pkg_webhook --> pkg_session_title
pkg_webhook --> pkg_workspace
pkg_subagent --> pkg_agent
pkg_subagent --> pkg_agent_presets
pkg_subagent --> pkg_agent_preset_registry
pkg_subagent --> pkg_attachment
pkg_subagent --> pkg_invariants
pkg_subagent --> pkg_jobs
@@ -1184,7 +1184,7 @@ flowchart TD
pkg_hooks_claude_code --> pkg_tools
pkg_api_session_controller --> pkg_agent
pkg_api_session_controller --> pkg_agent_default_model
pkg_api_session_controller --> pkg_agent_presets
pkg_api_session_controller --> pkg_agent_preset_registry
pkg_api_session_controller --> pkg_api_gateway
pkg_api_session_controller --> pkg_attachment
pkg_api_session_controller --> pkg_client_connection
@@ -1569,7 +1569,7 @@ flowchart TD
| [`tool-jobs`](../packages/jobs/tool-jobs) | `jobs` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`mcp-resources`](../packages/mcp/mcp-resources) | `mcp` | [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`agent-presets`](../packages/preset/agent-presets) | `preset` | [`agent`](../packages/core/agent), [`atomic-write`](../packages/util/atomic-write), [`home-paths`](../packages/util/home-paths), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`agent-preset-registry`](../packages/preset/agent-preset-registry) | `preset` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol) |
| [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`tools`](../packages/core/tools) |
| [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy) | `session` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`tools`](../packages/core/tools) |
| [`session-title-all-prompts-llm`](../packages/session/session-title-all-prompts-llm) | `session` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`session-title-llm`](../packages/session/session-title-llm) |
@@ -1581,15 +1581,16 @@ flowchart TD
| [`tool-terminal`](../packages/terminal/tool-terminal) | `terminal` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`system-prompt`](../packages/core/system-prompt), [`terminal`](../packages/terminal/terminal), [`tools`](../packages/core/tools) |
| [`llm-replay`](../packages/test-support/llm-replay) | `test-support` | [`compaction`](../packages/compaction/compaction), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
| [`plugin-package-inventory-deepseek`](../packages/llm/plugin-package-inventory-deepseek) | `llm` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`deepseek-llm-api-extensions`](../packages/llm/deepseek-llm-api-extensions), [`session`](../packages/core/session) |
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`compaction`](../packages/compaction/compaction), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm`](../packages/llm/llm), [`llm-retry`](../packages/llm/llm-retry), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
| [`session-query`](../packages/session-query/session-query) | `session-query` | [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`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-title`](../packages/session/session-title), [`tool-todo`](../packages/todo/tool-todo) |
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-presets`](../packages/preset/agent-presets), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`api-settings-controller`](../packages/api/settings-controller) | `api` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`credentials`](../packages/credentials/credentials), [`native-command`](../packages/util/native-command), [`session`](../packages/core/session), [`settings`](../packages/settings/settings), [`typert-protocol`](../packages/typert/protocol) |
| [`web-app`](../packages/bundle/web-app) | `bundle` | [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt) |
| [`experimental-auto-review`](../packages/experimental/auto-review) | `experimental` | [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
| [`tool-cordis`](../packages/extensions/tool-cordis) | `extensions` | [`agent`](../packages/core/agent), [`cordis-host-runner`](../packages/extensions/cordis-host-runner), [`tools`](../packages/core/tools) |
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-presets`](../packages/preset/agent-presets), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
| [`host-plugin-inventory`](../packages/host/plugin-inventory) | `host` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`typert-protocol`](../packages/typert/protocol) |
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-resources`](../packages/mcp/mcp-resources), [`scope`](../packages/core/scope), [`subprocess`](../packages/subprocess/subprocess), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`agent-preset`](../packages/preset/agent-preset) | `preset` | [`agent-preset-registry`](../packages/preset/agent-preset-registry) |
| [`session-telemetry-otel`](../packages/session/session-telemetry-otel) | `session` | [`anonymous-user-id`](../packages/identity/anonymous-user-id), [`command-feedback`](../packages/feedback/command-feedback), [`llm`](../packages/llm/llm), [`message-feedback`](../packages/feedback/message-feedback), [`session`](../packages/core/session), [`session-telemetry`](../packages/session/session-telemetry) |
| [`tool-bash`](../packages/shell/tool-bash) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
| [`tool-pwsh`](../packages/shell/tool-pwsh) | `shell` | [`agent`](../packages/core/agent), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`shell`](../packages/shell/shell), [`shell-env`](../packages/shell/shell-env), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`user-approval`](../packages/interaction/user-approval) |
@@ -1597,8 +1598,8 @@ flowchart TD
| [`sandbox-ssh`](../packages/ssh/sandbox-ssh) | `ssh` | [`sandbox`](../packages/sandbox/sandbox), [`ssh`](../packages/ssh/ssh) |
| [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-presets`](../packages/preset/agent-presets), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`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), [`session-query`](../packages/session-query/session-query), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
| [`webhook`](../packages/webhook/webhook) | `webhook` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`session`](../packages/core/session), [`session-title`](../packages/session/session-title), [`workspace`](../packages/workspace/workspace) |
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`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), [`session-query`](../packages/session-query/session-query), [`settings`](../packages/settings/settings), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time) |
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`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` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
@@ -1614,7 +1615,7 @@ flowchart TD
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`jobs`](../packages/jobs/jobs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`settings`](../packages/settings/settings), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
| [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | `subagent` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`hooks-claude-code`](../packages/hooks/hooks-claude-code) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-presets`](../packages/preset/agent-presets), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`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), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
| [`api-session-controller`](../packages/api/session-controller) | `api` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`api-gateway`](../packages/api/gateway), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-file-upload`](../packages/client/file-upload), [`commands`](../packages/interaction/commands), [`file-reference`](../packages/context/file-reference), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`native-command`](../packages/util/native-command), [`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), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`skill`](../packages/skill/skill), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol), [`typert-registry`](../packages/typert/registry), [`util-time`](../packages/util/time), [`util-values`](../packages/util/values), [`util-workspace-path`](../packages/util/workspace-path), [`workspace`](../packages/workspace/workspace) |
| [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) |
| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) |
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
persistence-catalog.md: 45fe3d7549b98a1c199e3bba1565fbf2c11b3620
persistence-catalog.zh.md: f55524fdf2f0c4dc3167a89249be8e703bfca7c3
persistence-catalog.md: 30fc6dc277bdbdaf5330740a8a291101358dbaa2
persistence-catalog.zh.md: fff627b8ff3a8c1c97f7793b0abca7008f395eb0
+2 -2
View File
@@ -198,7 +198,7 @@ Source: [`packages/core/agent/src/types.ts:87`](../packages/core/agent/src/types
'agent-preset/selected': { agentPreset: string }
```
Source: [`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts)
Source: [`packages/preset/agent-preset-registry/src/session.ts:28`](../packages/preset/agent-preset-registry/src/session.ts)
### `approval/*`
@@ -5501,7 +5501,7 @@ Sources: [`packages/plan/plan-mode/src/index.ts:53`](../packages/plan/plan-mode/
SHA-256: `6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406`
Sources: [`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts)
Sources: [`packages/preset/agent-preset-registry/src/session.ts:28`](../packages/preset/agent-preset-registry/src/session.ts)
| Property | Presence | Type |
|---|---|---|
+2 -2
View File
@@ -200,7 +200,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
'agent-preset/selected': { agentPreset: string }
```
来源:[`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts)
来源:[`packages/preset/agent-preset-registry/src/session.ts:28`](../packages/preset/agent-preset-registry/src/session.ts)
### `approval/*`
@@ -5503,7 +5503,7 @@ SHA-256: `20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5`
SHA-256: `6e52dd55a1d48954ed5464ec46fe34d9eb65f709baabc9561e7a279910b0c406`
来源:[`packages/preset/agent-presets/src/session.ts:28`](../packages/preset/agent-presets/src/session.ts)
来源:[`packages/preset/agent-preset-registry/src/session.ts:28`](../packages/preset/agent-preset-registry/src/session.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
+1 -1
View File
@@ -58852,7 +58852,7 @@
},
"names": [],
"sources": [
"packages/preset/agent-presets/src/session.ts:28"
"packages/preset/agent-preset-registry/src/session.ts:28"
]
},
{
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/core.md
core.md: a1e876dcfcd31dfb9fd4840d0d0e217bb71e43bb
core.zh.md: a63e38cf6381d97656f2350cc16502030772b1a7
core.md: fafe23cb473e30c74c17308f4ab79d982f609452
core.zh.md: 5945720e38a89846ae14694dd7cf2b45af7433ad
+54 -211
View File
@@ -481,248 +481,91 @@ Types: [SessionHeader](persistence.md)
Source: [`packages/core/agent-loop/src/index.ts`](../../packages/core/agent-loop/src/index.ts)
<a id="ctxagentpresets--agentpresets"></a>
<a id="ctxagentpresets--agentpresetregistry"></a>
### `ctx.agentPresets` — `AgentPresets`
### `ctx.agentPresets` — `AgentPresetRegistry`
Registry over the deployment's agent presets.
Discovery is unmemoized: `list()` and `resolve()` re-read the roots on every call so a preset authored while the process runs is visible immediately, and a preset deleted underneath a picker disappears from the next read.
Registry of YAML-declared presets and the revisions live Agents retain.
```ts cordis-catalog
/**
* Every preset the configured roots currently supply.
* @returns the presets, first-root-wins per id.
/** Register and eagerly load a definition; activation failure remains visible in the roster.
* @param definition Parsed configuration supplied by the declaring plugin.
* @returns Definition disposer after activation or its diagnostic settles; the declaring plugin owns it.
*/
async register(definition: PresetDefinition): Promise<() => Promise<void>>
/** Read every declared preset, including activation failures.
* @returns Display metadata and loading diagnostics.
*/
async list(): Promise<AgentPreset[]>
/**
* The roster off the Host: {@link list} projected to path-free rows, with
* the policy-effective default marked, this deployment's authoring
* capability, and its mode-selection policy beside it.
*
* Whether a client can open a preset's directory is the Host's own opener
* capability, not a roster property — a caller needing both joins them.
* @returns the rows, authoring capability, and effective selection policy.
/** Read the selection roster and chooser policy.
* @returns Current presets, default and chooser policy.
*/
@Remote('list') async remoteExportList(): Promise<AgentPresetRoster>
/**
* Every preset's composition as flattened plugin rows, for plugin-listing
* surfaces beside the roster's own picker.
*
* A preset with a live standing mount answers from its newest generation's
* Loader entries — the composition new sessions join — even when the file
* behind it has since been edited into an unreadable state: the mount is
* what sessions actually run, so the broken verdict only applies to a
* preset nothing composed. One never composed since boot answers from its
* file, with `!!js` disabled gates evaluated against the Loader context so
* both answers reflect the same host. Reading never mounts: an unmounted
* preset is parsed, not composed, so listing a preset's plugins cannot
* activate them early. A composition that stopped reading between
* discovery's health verdict and this read is reported broken with the
* raced reason rather than dropped.
* @returns one composition per roster preset, in roster order.
*/
async compositionInventory(): Promise<AgentPresetComposition[]>
/**
* Resolve one preset by id.
*
* A broken preset resolves — deleting one, reading one, and reporting one
* all need the row — and the mounting paths refuse it AFTER resolution
* through {@link resolveMountable}.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the resolved preset.
* @throws when no configured root supplies that id.
/** Resolve an identity without starting an Agent.
* @param id Explicit preset or the current default.
* @returns Current metadata, including failure when activation failed.
*/
async resolve(id?: string): Promise<AgentPreset>
/**
* Compose one agent from a preset: ensure the preset's standing mount, then
* parent the agent's scope key to it so the mount's registrations and
* listeners cover this agent.
*
* Call from the agent factory's `setup(agentCtx)`; a rejection there rolls
* the agent creation back, so a broken preset never yields a half-composed
* session.
* @param agentCtx - the agent's scope context.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the preset that was composed, for the caller to record.
* @throws when the preset is unknown or its composition is unusable.
/** Bind an unpublished Agent to the current preset revision.
* @param ctx Agent context from its setup callback.
* @param id Requested preset, or the default.
* @returns Bound preset identity.
*/
async mount(agentCtx: Context, id?: string): Promise<AgentPreset>
async mount(ctx: Context, id?: string): Promise<AgentPreset>
/**
* Join one agent to the SAME standing composition another already runs on.
*
* This is how a child agent inherits its parent's capabilities. It is a bind,
* not a mount: the parent's generation is already composed, so the child gets
* that exact instance — the same plugin objects, the same tool registrations,
* the same prompt sections. Re-resolving the parent's preset by id instead
* would re-read the roster, and a composition file edited since the parent
* started would hand the child a DIFFERENT generation than the one its
* parent's history was produced under (and a preset deleted since would fail
* the child outright while its parent keeps running).
*
* Synchronous, and with no composition failure mode of its own — it reads no
* roster, mounts nothing, and touches no file — which is what lets a child
* creation window use it: the two in-process subagent drivers compose their
* children inside a synchronous `setup`. It still rejects a caller error, as
* the `@throws` below record.
*
* A parent that joined no preset — a rosterless deployment — yields no join
* and no error: there, the model-facing rows sit in the host composition and
* the child already sees them through the global layer.
* @param agentCtx - the joining agent's scope context.
* @param parentCtx - the scope context of the agent whose composition to join.
* @returns the preset id joined, or undefined when the parent joined none.
* @throws when `agentCtx` carries no scope, or has already joined a preset.
/** Join a child to the exact revision retained by its parent.
* @param ctx Child Agent context.
* @param parent Parent Agent context.
* @returns Inherited preset id, or undefined in a preset-free composition.
*/
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined
composeFrom(ctx: Context, parent: Context): string | undefined
/**
* The preset one live agent runs on.
*
* Read from the live scope chain rather than from the session, so it answers
* for an agent whose session has not recorded a preset yet — a child agent
* whose durable header is being built from its parent's composition.
* @param agentCtx - the agent's scope context.
* @returns the preset id, or undefined when the agent joined none.
/** Read the preset a live Agent uses.
* @param ctx Agent context.
* @returns Its preset id, if bound.
*/
composedPreset(agentCtx: Context): string | undefined
composedPreset(ctx: Context): string | undefined
/**
* Read one preset's composition text.
* @param id - the preset id.
* @returns the composition exactly as stored.
* @throws when no configured root supplies that id.
*/
async read(id: string): Promise<string>
/**
* One preset's composition text with the roster row it belongs to.
* @param agentPreset - the preset id.
* @returns the composition beside its trust and published metadata.
* @throws {RemoteError} `gateway/bad-request` for an empty id, or
* `agent-preset/not-found` when no configured root supplies it.
*/
@Remote('read') async readDocument(agentPreset: string): Promise<AgentPresetDocument>
/**
* Create a locally authored preset by copying an existing one whole.
*
* Copy is the only authoring write. Composition text never crosses this
* seam: the source is named by id and its directory is copied as it stands,
* so the copy is exactly as loadable as its source and authoring grants no
* capability the roster did not already carry. The copy is NOT mounted to
* validate — a source that mounts today yields a copy that mounts today.
* @param from - the preset the copy starts from; shipped presets are the
* primary source, so any trust is accepted.
* @param id - the new preset's id, which becomes its directory name.
* @param name - display name for the copy; absent falls back to the id.
* @throws when the source is unknown, the id is unusable or already taken,
* or the deployment configures no writable root.
*/
async copy(from: string, id: string, name?: string): Promise<void>
/**
* Copy one preset through the Remote API.
* @param from - the source preset id.
* @param id - the new preset id.
* @param name - the copy's optional display name.
* @returns once the copy is stored.
* @throws {RemoteError} with the corresponding stable preset code and
* details when the copy is refused.
*/
@Remote('copy') async remoteExportCopy(from: string, id: string, name?: string): Promise<void>
/**
* Delete a locally authored preset.
*
* @param id - the preset id.
* @throws when the preset is unknown or ships with the deployment.
*/
async remove(id: string): Promise<void>
/**
* Delete one preset through the Remote API.
* @param id - the preset id.
* @returns once the preset is deleted.
* @throws {RemoteError} with the corresponding stable preset code and
* details when deletion is refused.
*/
@Remote('deletePreset') async remoteExportDelete(id: string): Promise<void>
/**
* One agent's instance of a service its preset mounted.
*
* A preset publishes services behind `isolate` realms, which are invisible
* outside the group that declares them — including to the host. This is how a
* caller holding the agent reads one anyway: a request that is ABOUT a
* session but arrives from outside it, which is every browser RPC.
*
* Read addressing only. A host row that `inject`s a service cannot use this,
* because injection resolves before any session exists and has no agent to
* key by; such a service belongs on the host plane instead.
* @param agent - the agent whose composition to look inside.
* @param name - the service name as the preset's rows resolve it.
* @returns the agent's instance, or undefined when its preset mounts none.
/** Read a service supplied inside an Agent's isolated preset group.
* @param agent Agent whose composition is queried.
* @param name Cordis service name.
* @returns The service, or undefined.
*/
serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K): Context[K] | undefined
/**
* Re-link one agent to a different preset's standing composition.
*
* Only valid while the agent has produced nothing: swapping tools mid
* conversation would leave logged tool calls the new composition cannot
* make. The CALLER owns that check — this method does not read session
* history.
*
* The swap is a parent re-link, not an unmount: standing mounts are shared
* and permanent, so the old composition stays for its other agents and the
* new one is ensured BEFORE the link moves. An unknown or unusable preset
* therefore throws with the agent exactly as it was — there is no torn-down
* state to restore. The re-link runs through the binding this roster kept
* from the agent's mount — dsh-scope's only re-link authority. An agent
* that never composed one has nothing to re-link: the switch is then the
* agent's first bind, exactly a mount. A committed re-link emits
* `tools/change` because changing the parent scope changes the Agent's
* resolved tool set without adding or removing registry entries.
* @param agentCtx - the agent's scope context.
* @param id - the preset to compose the agent from instead.
* @returns the preset now installed.
* @throws when the preset is unknown or its composition is unusable.
/** Rebind a blank Agent; the caller owns the blank-session check.
* @param ctx Agent context.
* @param id Requested preset.
* @returns The bound identity.
*/
async recompose(agentCtx: Context, id: string): Promise<AgentPreset>
async recompose(ctx: Context, id: string): Promise<AgentPreset>
/**
* Compose a blank session's agent from a different preset and record it.
* @param agent - the session's live agent, resolved from the wire identity.
* @param agentPreset - the preset to compose the agent from instead.
* @returns the preset id that was recorded.
* @throws {RemoteError} with `gateway/bad-request`, `agent-preset/locked`,
* `agent-preset/not-found`, or `agent-preset/invalid` when refused.
/** Select a preset before a session starts its first turn.
* @param agent Target Agent.
* @param agentPreset Requested identity.
* @returns Committed preset identity.
*/
@Remote('select') async select(agent: Agent, agentPreset: string): Promise<string>
/**
* The standing scope key of one preset, for a host reader with no agent.
*
* A cold transcript read resolves tool presenters against the composition
* the session recorded, and the standing mount makes that possible without
* resuming anything: ensuring the mount composes plugins but starts no
* agent, no session, and no turn.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the standing scope key readers pass as a registry view scope.
* @throws when the preset is unknown or its composition is unusable.
/** Read current registrations for cold transcript presentation.
* @param id Preset identity or the default.
* @returns A revision lease; dispose it after the scoped read completes.
*/
async standingKeyFor(id?: string): Promise<ScopeKey>
async acquireScope(id?: string): Promise<{ key: ScopeKey } & AsyncDisposable>
/** Read plugin rows without creating an Agent.
* @returns Current declaration metadata and activation states.
*/
compositionInventory(): Promise<AgentPresetComposition[]>
```
Types: [ScopeKey](scope.md)
Source: [`packages/preset/agent-presets/src/index.ts`](../../packages/preset/agent-presets/src/index.ts)
Source: [`packages/preset/agent-preset-registry/src/index.ts`](../../packages/preset/agent-preset-registry/src/index.ts)
<a id="ctxagents--agentregistry"></a>
@@ -1251,5 +1094,5 @@ One session committed a different agent preset to its durable log. Consumers inv
'agent-preset/selected'(sessionId: SessionId, agentPreset: string): void
```
Source: [`packages/preset/agent-presets/src/types.ts`](../../packages/preset/agent-presets/src/types.ts)
Source: [`packages/preset/agent-preset-registry/src/types.ts`](../../packages/preset/agent-preset-registry/src/types.ts)
<!-- END GENERATED cordis-surface -->
+54 -211
View File
@@ -491,248 +491,91 @@ Types: [SessionHeader](persistence.zh.md)
Source: [`packages/core/agent-loop/src/index.ts`](../../packages/core/agent-loop/src/index.ts)
<a id="ctxagentpresets--agentpresets"></a>
<a id="ctxagentpresets--agentpresetregistry"></a>
### `ctx.agentPresets` — `AgentPresets`
### `ctx.agentPresets` — `AgentPresetRegistry`
Registry over the deployment's agent presets.
Discovery is unmemoized: `list()` and `resolve()` re-read the roots on every call so a preset authored while the process runs is visible immediately, and a preset deleted underneath a picker disappears from the next read.
Registry of YAML-declared presets and the revisions live Agents retain.
```ts cordis-catalog
/**
* Every preset the configured roots currently supply.
* @returns the presets, first-root-wins per id.
/** Register and eagerly load a definition; activation failure remains visible in the roster.
* @param definition Parsed configuration supplied by the declaring plugin.
* @returns Definition disposer after activation or its diagnostic settles; the declaring plugin owns it.
*/
async register(definition: PresetDefinition): Promise<() => Promise<void>>
/** Read every declared preset, including activation failures.
* @returns Display metadata and loading diagnostics.
*/
async list(): Promise<AgentPreset[]>
/**
* The roster off the Host: {@link list} projected to path-free rows, with
* the policy-effective default marked, this deployment's authoring
* capability, and its mode-selection policy beside it.
*
* Whether a client can open a preset's directory is the Host's own opener
* capability, not a roster property — a caller needing both joins them.
* @returns the rows, authoring capability, and effective selection policy.
/** Read the selection roster and chooser policy.
* @returns Current presets, default and chooser policy.
*/
@Remote('list') async remoteExportList(): Promise<AgentPresetRoster>
/**
* Every preset's composition as flattened plugin rows, for plugin-listing
* surfaces beside the roster's own picker.
*
* A preset with a live standing mount answers from its newest generation's
* Loader entries — the composition new sessions join — even when the file
* behind it has since been edited into an unreadable state: the mount is
* what sessions actually run, so the broken verdict only applies to a
* preset nothing composed. One never composed since boot answers from its
* file, with `!!js` disabled gates evaluated against the Loader context so
* both answers reflect the same host. Reading never mounts: an unmounted
* preset is parsed, not composed, so listing a preset's plugins cannot
* activate them early. A composition that stopped reading between
* discovery's health verdict and this read is reported broken with the
* raced reason rather than dropped.
* @returns one composition per roster preset, in roster order.
*/
async compositionInventory(): Promise<AgentPresetComposition[]>
/**
* Resolve one preset by id.
*
* A broken preset resolves — deleting one, reading one, and reporting one
* all need the row — and the mounting paths refuse it AFTER resolution
* through {@link resolveMountable}.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the resolved preset.
* @throws when no configured root supplies that id.
/** Resolve an identity without starting an Agent.
* @param id Explicit preset or the current default.
* @returns Current metadata, including failure when activation failed.
*/
async resolve(id?: string): Promise<AgentPreset>
/**
* Compose one agent from a preset: ensure the preset's standing mount, then
* parent the agent's scope key to it so the mount's registrations and
* listeners cover this agent.
*
* Call from the agent factory's `setup(agentCtx)`; a rejection there rolls
* the agent creation back, so a broken preset never yields a half-composed
* session.
* @param agentCtx - the agent's scope context.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the preset that was composed, for the caller to record.
* @throws when the preset is unknown or its composition is unusable.
/** Bind an unpublished Agent to the current preset revision.
* @param ctx Agent context from its setup callback.
* @param id Requested preset, or the default.
* @returns Bound preset identity.
*/
async mount(agentCtx: Context, id?: string): Promise<AgentPreset>
async mount(ctx: Context, id?: string): Promise<AgentPreset>
/**
* Join one agent to the SAME standing composition another already runs on.
*
* This is how a child agent inherits its parent's capabilities. It is a bind,
* not a mount: the parent's generation is already composed, so the child gets
* that exact instance — the same plugin objects, the same tool registrations,
* the same prompt sections. Re-resolving the parent's preset by id instead
* would re-read the roster, and a composition file edited since the parent
* started would hand the child a DIFFERENT generation than the one its
* parent's history was produced under (and a preset deleted since would fail
* the child outright while its parent keeps running).
*
* Synchronous, and with no composition failure mode of its own — it reads no
* roster, mounts nothing, and touches no file — which is what lets a child
* creation window use it: the two in-process subagent drivers compose their
* children inside a synchronous `setup`. It still rejects a caller error, as
* the `@throws` below record.
*
* A parent that joined no preset — a rosterless deployment — yields no join
* and no error: there, the model-facing rows sit in the host composition and
* the child already sees them through the global layer.
* @param agentCtx - the joining agent's scope context.
* @param parentCtx - the scope context of the agent whose composition to join.
* @returns the preset id joined, or undefined when the parent joined none.
* @throws when `agentCtx` carries no scope, or has already joined a preset.
/** Join a child to the exact revision retained by its parent.
* @param ctx Child Agent context.
* @param parent Parent Agent context.
* @returns Inherited preset id, or undefined in a preset-free composition.
*/
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined
composeFrom(ctx: Context, parent: Context): string | undefined
/**
* The preset one live agent runs on.
*
* Read from the live scope chain rather than from the session, so it answers
* for an agent whose session has not recorded a preset yet — a child agent
* whose durable header is being built from its parent's composition.
* @param agentCtx - the agent's scope context.
* @returns the preset id, or undefined when the agent joined none.
/** Read the preset a live Agent uses.
* @param ctx Agent context.
* @returns Its preset id, if bound.
*/
composedPreset(agentCtx: Context): string | undefined
composedPreset(ctx: Context): string | undefined
/**
* Read one preset's composition text.
* @param id - the preset id.
* @returns the composition exactly as stored.
* @throws when no configured root supplies that id.
*/
async read(id: string): Promise<string>
/**
* One preset's composition text with the roster row it belongs to.
* @param agentPreset - the preset id.
* @returns the composition beside its trust and published metadata.
* @throws {RemoteError} `gateway/bad-request` for an empty id, or
* `agent-preset/not-found` when no configured root supplies it.
*/
@Remote('read') async readDocument(agentPreset: string): Promise<AgentPresetDocument>
/**
* Create a locally authored preset by copying an existing one whole.
*
* Copy is the only authoring write. Composition text never crosses this
* seam: the source is named by id and its directory is copied as it stands,
* so the copy is exactly as loadable as its source and authoring grants no
* capability the roster did not already carry. The copy is NOT mounted to
* validate — a source that mounts today yields a copy that mounts today.
* @param from - the preset the copy starts from; shipped presets are the
* primary source, so any trust is accepted.
* @param id - the new preset's id, which becomes its directory name.
* @param name - display name for the copy; absent falls back to the id.
* @throws when the source is unknown, the id is unusable or already taken,
* or the deployment configures no writable root.
*/
async copy(from: string, id: string, name?: string): Promise<void>
/**
* Copy one preset through the Remote API.
* @param from - the source preset id.
* @param id - the new preset id.
* @param name - the copy's optional display name.
* @returns once the copy is stored.
* @throws {RemoteError} with the corresponding stable preset code and
* details when the copy is refused.
*/
@Remote('copy') async remoteExportCopy(from: string, id: string, name?: string): Promise<void>
/**
* Delete a locally authored preset.
*
* @param id - the preset id.
* @throws when the preset is unknown or ships with the deployment.
*/
async remove(id: string): Promise<void>
/**
* Delete one preset through the Remote API.
* @param id - the preset id.
* @returns once the preset is deleted.
* @throws {RemoteError} with the corresponding stable preset code and
* details when deletion is refused.
*/
@Remote('deletePreset') async remoteExportDelete(id: string): Promise<void>
/**
* One agent's instance of a service its preset mounted.
*
* A preset publishes services behind `isolate` realms, which are invisible
* outside the group that declares them — including to the host. This is how a
* caller holding the agent reads one anyway: a request that is ABOUT a
* session but arrives from outside it, which is every browser RPC.
*
* Read addressing only. A host row that `inject`s a service cannot use this,
* because injection resolves before any session exists and has no agent to
* key by; such a service belongs on the host plane instead.
* @param agent - the agent whose composition to look inside.
* @param name - the service name as the preset's rows resolve it.
* @returns the agent's instance, or undefined when its preset mounts none.
/** Read a service supplied inside an Agent's isolated preset group.
* @param agent Agent whose composition is queried.
* @param name Cordis service name.
* @returns The service, or undefined.
*/
serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K): Context[K] | undefined
/**
* Re-link one agent to a different preset's standing composition.
*
* Only valid while the agent has produced nothing: swapping tools mid
* conversation would leave logged tool calls the new composition cannot
* make. The CALLER owns that check — this method does not read session
* history.
*
* The swap is a parent re-link, not an unmount: standing mounts are shared
* and permanent, so the old composition stays for its other agents and the
* new one is ensured BEFORE the link moves. An unknown or unusable preset
* therefore throws with the agent exactly as it was — there is no torn-down
* state to restore. The re-link runs through the binding this roster kept
* from the agent's mount — dsh-scope's only re-link authority. An agent
* that never composed one has nothing to re-link: the switch is then the
* agent's first bind, exactly a mount. A committed re-link emits
* `tools/change` because changing the parent scope changes the Agent's
* resolved tool set without adding or removing registry entries.
* @param agentCtx - the agent's scope context.
* @param id - the preset to compose the agent from instead.
* @returns the preset now installed.
* @throws when the preset is unknown or its composition is unusable.
/** Rebind a blank Agent; the caller owns the blank-session check.
* @param ctx Agent context.
* @param id Requested preset.
* @returns The bound identity.
*/
async recompose(agentCtx: Context, id: string): Promise<AgentPreset>
async recompose(ctx: Context, id: string): Promise<AgentPreset>
/**
* Compose a blank session's agent from a different preset and record it.
* @param agent - the session's live agent, resolved from the wire identity.
* @param agentPreset - the preset to compose the agent from instead.
* @returns the preset id that was recorded.
* @throws {RemoteError} with `gateway/bad-request`, `agent-preset/locked`,
* `agent-preset/not-found`, or `agent-preset/invalid` when refused.
/** Select a preset before a session starts its first turn.
* @param agent Target Agent.
* @param agentPreset Requested identity.
* @returns Committed preset identity.
*/
@Remote('select') async select(agent: Agent, agentPreset: string): Promise<string>
/**
* The standing scope key of one preset, for a host reader with no agent.
*
* A cold transcript read resolves tool presenters against the composition
* the session recorded, and the standing mount makes that possible without
* resuming anything: ensuring the mount composes plugins but starts no
* agent, no session, and no turn.
* @param id - the preset id, or `undefined` for {@link defaultId}.
* @returns the standing scope key readers pass as a registry view scope.
* @throws when the preset is unknown or its composition is unusable.
/** Read current registrations for cold transcript presentation.
* @param id Preset identity or the default.
* @returns A revision lease; dispose it after the scoped read completes.
*/
async standingKeyFor(id?: string): Promise<ScopeKey>
async acquireScope(id?: string): Promise<{ key: ScopeKey } & AsyncDisposable>
/** Read plugin rows without creating an Agent.
* @returns Current declaration metadata and activation states.
*/
compositionInventory(): Promise<AgentPresetComposition[]>
```
Types: [ScopeKey](scope.zh.md)
Source: [`packages/preset/agent-presets/src/index.ts`](../../packages/preset/agent-presets/src/index.ts)
Source: [`packages/preset/agent-preset-registry/src/index.ts`](../../packages/preset/agent-preset-registry/src/index.ts)
<a id="ctxagents--agentregistry"></a>
@@ -1261,5 +1104,5 @@ One session committed a different agent preset to its durable log. Consumers inv
'agent-preset/selected'(sessionId: SessionId, agentPreset: string): void
```
Source: [`packages/preset/agent-presets/src/types.ts`](../../packages/preset/agent-presets/src/types.ts)
Source: [`packages/preset/agent-preset-registry/src/types.ts`](../../packages/preset/agent-preset-registry/src/types.ts)
<!-- END GENERATED cordis-surface -->
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/settings.md
settings.md: d8e3cbc46eb697315d4938b696e921a0c2e11828
settings.zh.md: 772fd7832ff80cab9444c16c9a1dc7ae1875d51e
settings.md: 875b25f58dea82a2a69f2d4efa7696bdb39c257d
settings.zh.md: 88c50e5f991ec5b59a1f5b2ce3fc98c1bb84936d
+1 -16
View File
@@ -163,7 +163,7 @@ type SettingsUpdateSource = 'update' | 'provider'
## Native document operations
`SettingsDocumentOpenValue` confirms that `settings/openSettingsDocument` prepared the provider-owned document and handed it to the native text editor. `AgentPresetDirectoryOpenValue` reports either a completed native handoff or the resolved user-preset directory when desktop opening is unavailable. Neither operation accepts a browser-selected Host path.
`SettingsDocumentOpenValue` confirms that `settings/openSettingsDocument` prepared the provider-owned document and handed it to the native text editor. This operation does not accept a browser-selected Host path.
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
@@ -291,12 +291,6 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
*/
@Remote describe(): SettingsDescribeValue
/**
* Report whether this deployment can open an authored Agent preset directory natively.
* @returns true when the matching open operation is available.
*/
@Remote canOpenAgentPresetDirectory(): boolean
/**
* Merge a patch into one namespace's stored user section.
* @param ns - namespace key to write.
@@ -336,15 +330,6 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
* @throws RemoteError when no document exists, preparation fails, or opening fails.
*/
@Remote async openSettingsDocument(signal: AbortSignal): Promise<SettingsDocumentOpenValue>
/**
* Open one user-authored Agent preset directory or return its path when no native opener exists.
* @param agentPreset - preset id resolved against Host-owned roots.
* @param signal - caller lifetime; abort terminates the native command.
* @returns an opened confirmation or the resolved directory for text display.
* @throws RemoteError when the preset is missing, read-only, invalid, or cannot be opened.
*/
@Remote async openAgentPresetDirectory( agentPreset: string, signal: AbortSignal, ): Promise<AgentPresetDirectoryOpenValue>
```
Source: [`packages/api/settings-controller/src/index.ts`](../../packages/api/settings-controller/src/index.ts)
+1 -16
View File
@@ -163,7 +163,7 @@ type SettingsUpdateSource = 'update' | 'provider'
## 原生文档操作
`SettingsDocumentOpenValue` 确认 `settings/openSettingsDocument` 已准备好 provider 持有的文档,并将其交给原生文本编辑器。`AgentPresetDirectoryOpenValue` 报告已完成的原生交接,或在桌面打开不可用时返回解析后的用户 preset 目录。两项操作都不接受由浏览器选择的 Host 路径。
`SettingsDocumentOpenValue` 确认 `settings/openSettingsDocument` 已准备好 provider 持有的文档,并将其交给原生文本编辑器。此操作不接受由浏览器选择的 Host 路径。
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
@@ -291,12 +291,6 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
*/
@Remote describe(): SettingsDescribeValue
/**
* Report whether this deployment can open an authored Agent preset directory natively.
* @returns true when the matching open operation is available.
*/
@Remote canOpenAgentPresetDirectory(): boolean
/**
* Merge a patch into one namespace's stored user section.
* @param ns - namespace key to write.
@@ -336,15 +330,6 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
* @throws RemoteError when no document exists, preparation fails, or opening fails.
*/
@Remote async openSettingsDocument(signal: AbortSignal): Promise<SettingsDocumentOpenValue>
/**
* Open one user-authored Agent preset directory or return its path when no native opener exists.
* @param agentPreset - preset id resolved against Host-owned roots.
* @param signal - caller lifetime; abort terminates the native command.
* @returns an opened confirmation or the resolved directory for text display.
* @throws RemoteError when the preset is missing, read-only, invalid, or cannot be opened.
*/
@Remote async openAgentPresetDirectory( agentPreset: string, signal: AbortSignal, ): Promise<AgentPresetDirectoryOpenValue>
```
Source: [`packages/api/settings-controller/src/index.ts`](../../packages/api/settings-controller/src/index.ts)
+1 -1
View File
@@ -200,7 +200,7 @@ export function apply(ctx: Context, config: AcpConfig): void {
// No preset composition: the ACP bundle keeps the model-facing rows in
// the host plane, so this agent reads them from the global layer. A
// deployment that configures a roster has to join one here first
// (@deepseek-ai/dsh-agent-presets README, "Composing a child agent").
// (@deepseek-ai/dsh-agent-preset-registry README, "Composing a child agent").
let record: AcpSession
try {
record = await AcpSession.create(ctx, {
+1 -1
View File
@@ -61,7 +61,7 @@
"devDependencies": {
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
"@deepseek-ai/dsh-agent-preset-registry": "workspace:^",
"@deepseek-ai/dsh-api-gateway": "workspace:^",
"@deepseek-ai/dsh-api-job-controller": "workspace:^",
"@deepseek-ai/dsh-api-session-controller": "workspace:^",
+3 -3
View File
@@ -1,7 +1,7 @@
/** Platform-neutral assembly of generated Host Remote contributions. */
import type { Context } from '@deepseek-ai/cordis'
import agentPresetsRemote from '@deepseek-ai/dsh-agent-presets/remote'
import agentPresetsRemote from '@deepseek-ai/dsh-agent-preset-registry/remote'
import commandsRemote from '@deepseek-ai/dsh-commands/remote'
import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote'
import officeToPdfRemote from '@deepseek-ai/dsh-office-to-pdf/remote'
@@ -31,7 +31,7 @@ export type {
} from '@deepseek-ai/dsh-plugin-manager/types'
export type {} from '@deepseek-ai/dsh-plugin-manager/remote'
export type { PluginInventorySnapshot } from '@deepseek-ai/dsh-host-plugin-inventory/types'
export type {} from '@deepseek-ai/dsh-agent-presets/remote'
export type {} from '@deepseek-ai/dsh-agent-preset-registry/remote'
export type {} from '@deepseek-ai/dsh-commands/remote'
export type {} from '@deepseek-ai/dsh-api-settings-controller/remote'
export type {} from '@deepseek-ai/dsh-goal/remote'
@@ -65,7 +65,7 @@ export type {} from '@deepseek-ai/dsh-commands/types'
export type {} from '@deepseek-ai/dsh-cordis-host-runner/types'
export type {} from '@deepseek-ai/dsh-credentials/types'
export type {} from '@deepseek-ai/dsh-llm/types'
export type {} from '@deepseek-ai/dsh-agent-presets/types'
export type {} from '@deepseek-ai/dsh-agent-preset-registry/types'
export type {} from '@deepseek-ai/dsh-permission-presets/types'
export type {} from '@deepseek-ai/dsh-settings/types'
export type {} from '@deepseek-ai/dsh-user-approval/types'
+1 -1
View File
@@ -23,7 +23,7 @@ import type {} from '@deepseek-ai/dsh-cordis-host-runner/types'
import type {} from '@deepseek-ai/dsh-credentials/types'
import type {} from '@deepseek-ai/dsh-goal/types'
import type {} from '@deepseek-ai/dsh-llm/types'
import type {} from '@deepseek-ai/dsh-agent-presets/types'
import type {} from '@deepseek-ai/dsh-agent-preset-registry/types'
import type {} from '@deepseek-ai/dsh-permission-presets/types'
import type {} from '@deepseek-ai/dsh-settings/types'
import type {} from '@deepseek-ai/dsh-user-approval'
+1 -1
View File
@@ -54,7 +54,7 @@
"path": "../../llm/llm"
},
{
"path": "../../preset/agent-presets"
"path": "../../preset/agent-preset-registry"
},
{
"path": "../../settings/settings"
+1 -1
View File
@@ -36,7 +36,7 @@
"path": "../../goal/goal"
},
{
"path": "../../preset/agent-presets"
"path": "../../preset/agent-preset-registry"
},
{
"path": "../../extensions/cordis-host-runner"
+2 -2
View File
@@ -79,7 +79,7 @@
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-agent-default-model": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
"@deepseek-ai/dsh-agent-preset-registry": "workspace:^",
"@deepseek-ai/dsh-api-gateway": "workspace:^",
"@deepseek-ai/dsh-attachment": "workspace:^",
"@deepseek-ai/dsh-client-connection": "workspace:^",
@@ -119,7 +119,7 @@
"@deepseek-ai/dsh-agent-default-model": "workspace:^",
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-agent-presets": "workspace:^",
"@deepseek-ai/dsh-agent-preset-registry": "workspace:^",
"@deepseek-ai/dsh-api-gateway": "workspace:^",
"@deepseek-ai/dsh-attachment": "workspace:^",
"@deepseek-ai/dsh-client-connection": "workspace:^",
+1 -1
View File
@@ -7,7 +7,7 @@ import type {
Agent, AgentOptions, AgentSetup, ModelSelection as AgentModelSelection, ModelSelectionRef,
} from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-default-model'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import { ReasoningEffortId } from '@deepseek-ai/dsh-llm'
import type { Session, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionInspection } from '@deepseek-ai/dsh-session-persistence'
+1 -1
View File
@@ -1,7 +1,7 @@
/** Cold-safe Session list and search projection. */
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import type { ImageAttachmentLimits } from '@deepseek-ai/dsh-attachment'
import type { Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
import type { ProjectionSnapshot } from '@deepseek-ai/dsh-session-projection'
@@ -1,8 +1,7 @@
/** Session-addressed, cold-readable skill catalog Remote. */
import type { Context } from '@deepseek-ai/cordis'
import type {} from '@deepseek-ai/dsh-agent-presets/types'
import type { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-agent-preset-registry/types'
import { SessionQueryError } from '@deepseek-ai/dsh-session-query'
import { isUserInvocable } from '@deepseek-ai/dsh-skill'
import type { ScopeKey } from '@deepseek-ai/dsh-scope'
@@ -72,7 +71,8 @@ export class SessionSkillCatalog extends TypertRemoteService {
)
}
const scope = await this.scopeFor(sessionId, agentPreset)
await using lease = live === undefined ? await this.scopeFor(agentPreset) : undefined
const scope = live ?? lease?.key
try {
const skills = (await skillRegistry.list({ cwd, scope })).filter(isUserInvocable)
return {
@@ -91,15 +91,12 @@ export class SessionSkillCatalog extends TypertRemoteService {
/** Resolve a live or standing preset scope without creating an Agent. */
private async scopeFor(
sessionId: SessionId,
agentPreset: string | undefined,
): Promise<ScopeKey | undefined> {
const live = this.ctx.agents.get(sessionId)
if (live !== undefined) return live
): Promise<({ key: ScopeKey } & AsyncDisposable) | undefined> {
const presets = this.ctx.get('agentPresets')
if (presets === undefined) return undefined
try {
return await presets.standingKeyFor(agentPreset)
return await presets.acquireScope(agentPreset)
} catch {
// An unknown or unusable recorded preset falls back to the global registry.
return undefined
@@ -4,7 +4,7 @@ import { join } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
import SessionStore, { SESSION_FORMAT_VERSION, SessionLogOffset, SessionId } from '@deepseek-ai/dsh-session'
import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session'
import { SessionAlreadyOwnedError } from '@deepseek-ai/dsh-session-persistence'
@@ -1,7 +1,7 @@
import { Context } from '@deepseek-ai/cordis'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import type { Agent, AgentHandle, CreateAgentOptions } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-agent-presets'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
@@ -6,7 +6,7 @@ import { join } from 'node:path'
import { Context } from '@deepseek-ai/cordis'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import type { Agent, AgentFactory } from '@deepseek-ai/dsh-agent'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import type { Session } from '@deepseek-ai/dsh-session'
import { RemoteError } from '@deepseek-ai/dsh-typert-protocol'
@@ -29,7 +29,6 @@ function roster(ids: readonly string[]): unknown {
const presetOf = (id: string): object => ({
id,
trust: 'system',
path: `/presets/${id}/agent.cordis.yml`,
})
return {
defaultId: ids[0],
@@ -15,7 +15,7 @@ import { Context } from '@deepseek-ai/cordis'
import { z } from 'zod'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import { AttachmentStore } from '@deepseek-ai/dsh-attachment'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-presets'
import { agentPresetProjectionDefinition } from '@deepseek-ai/dsh-agent-preset-registry'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from '@deepseek-ai/dsh-session'
import type { Session, SessionEvent, SessionHeader, UserMessage } from '@deepseek-ai/dsh-session'
@@ -99,10 +99,10 @@ describe('SessionSkillCatalog', () => {
description: 'Composed for this Agent.',
invocation: { modelInvocable: false, userInvocable: true },
}]))
const standingKeyFor = vi.fn()
const acquireScope = vi.fn()
ctx.provide('agentPresets', {
serviceFor: () => ({ list: scopedList }),
standingKeyFor,
acquireScope,
} as never)
const catalog = new SessionSkillCatalog(ctx)
@@ -114,7 +114,7 @@ describe('SessionSkillCatalog', () => {
}],
})
expect(scopedList).toHaveBeenCalledWith({ cwd: '/live/project', scope: agent })
expect(standingKeyFor).not.toHaveBeenCalled()
expect(acquireScope).not.toHaveBeenCalled()
})
it('uses the recorded preset standing scope for a cold Session', async () => {
@@ -127,14 +127,14 @@ describe('SessionSkillCatalog', () => {
agentPreset: 'minimal',
})),
} as never)
const standingKeyFor = vi.fn(() => Promise.resolve(scope))
ctx.provide('agentPresets', { standingKeyFor } as never)
const acquireScope = vi.fn(() => Promise.resolve({ key: scope, [Symbol.asyncDispose]: async () => {} }))
ctx.provide('agentPresets', { acquireScope } as never)
const list = vi.fn(() => Promise.resolve([]))
ctx.provide('skills', { list } as never)
const catalog = new SessionSkillCatalog(ctx)
await expect(catalog.list({ sessionId }, new AbortController().signal)).resolves.toEqual({ skills: [] })
expect(standingKeyFor).toHaveBeenCalledWith('minimal')
expect(acquireScope).toHaveBeenCalledWith('minimal')
expect(list).toHaveBeenCalledWith({ cwd: '/cold/project', scope })
expect(ctx.agents.list()).toEqual([])
})
@@ -149,7 +149,7 @@ describe('SessionSkillCatalog', () => {
})),
} as never)
ctx.provide('agentPresets', {
standingKeyFor: () => Promise.reject(new Error('unknown preset')),
acquireScope: () => Promise.reject(new Error('unknown preset')),
} as never)
const list = vi.fn(() => Promise.resolve([]))
ctx.provide('skills', { list } as never)
@@ -38,7 +38,7 @@
{ "path": "../../llm/llm" },
{ "path": "../../util/deque" },
{ "path": "../../util/native-command" },
{ "path": "../../preset/agent-presets" },
{ "path": "../../preset/agent-preset-registry" },
{ "path": "../../session/session-persistence" },
{ "path": "../../session/session-projection" },
{ "path": "../../session/session-projection-cache" },
@@ -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/api/settings-controller/README.md
README.md: 761e4c43751675eec141a2efcf85caf52cc91590
README.zh.md: c5a59f3eebfd1e6702c2abcd2407bc2ef4e725bd
README.md: b7472f6b0f32eeb6d5f674da7187ae35f7d96936
README.zh.md: 9159e1f1678ec9a3fc282986ee89141e55ff3b4c
+1 -2
View File
@@ -29,7 +29,7 @@ Mount this package as a Loader entry in a profile that serves browser configurat
`settings.describe()` returns deployment facts and every namespace under `redactSecrets: true`. `settings.update`, `settings.replace`, and `settings.mutate` expose the settings service's three write operations and return the namespace's new redacted view; stale writes use `settings-conflict` and other provider refusals use `settings-rejected`.
`settings.openSettingsDocument()` prepares the provider-owned document and opens it with the native text-editor intent. `settings.canOpenAgentPresetDirectory()` reports native-opening availability when the preset page becomes visible. `settings.openAgentPresetDirectory(id)` resolves only a user-authored preset and either opens its directory or returns the path when native opening is unavailable; neither open method accepts a browser-supplied filesystem target.
`settings.openSettingsDocument()` prepares the provider-owned document and opens it with the native text editor; it accepts no browser-supplied filesystem target.
-----
@@ -38,7 +38,6 @@ Mount this package as a Loader entry in a profile that serves browser configurat
| Field | Default | Meaning |
|---|---|---|
| `nativeOpen` | platform-detected | Whether Agent preset directories can be handed to a native desktop opener |
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-api-settings-controller) is the exhaustive source for accepted fields and their JSDoc.
@@ -29,7 +29,7 @@ kind: "package-reference"
`settings.describe()` 返回部署信息,以及在 `redactSecrets: true` 下读取的所有 namespace。`settings.update`、`settings.replace` 与 `settings.mutate` 暴露 settings 服务的三种写入操作,并返回该 namespace 的新脱敏视图;陈旧写入使用 `settings-conflict`,其他提供方拒绝使用 `settings-rejected`。
`settings.openSettingsDocument()` 准备提供方持有的文档,并用原生文本编辑器意图将其打开。`settings.canOpenAgentPresetDirectory()` 在 preset 页面显示时报告原生打开能力。`settings.openAgentPresetDirectory(id)` 只解析用户创作的 preset,并打开其目录,或在原生打开不可用时返回目录路径;两个打开方法都不接受浏览器提供的文件系统目标。
`settings.openSettingsDocument()` 准备提供方持有的文档,并用原生文本编辑器打开;该方法不接受浏览器提供的文件系统目标。
-----
@@ -38,7 +38,6 @@ kind: "package-reference"
| 字段 | 默认值 | 含义 |
|---|---|---|
| `nativeOpen` | 平台探测 | Agent preset 目录能否交给原生桌面打开器 |
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-api-settings-controller)是所有受支持字段及其 JSDoc 的完整来源。
Loaded 100 of 294 files, more files were not shown because too many files have changed in this diff. Show more