Merge remote-tracking branch 'origin/master' into worktree/sleepy-grothendieck-64df7b
# Conflicts: # docs/event-producer-consumer.md # docs/event-producer-consumer.zh.md # docs/persistence-catalog.i18n.yaml # docs/persistence-catalog.md # docs/persistence-catalog.zh.md
This commit is contained in:
commit
5a461467db
262 files changed
+2804
-4040
No files matched your search
+6
-6
@@ -9,11 +9,11 @@
|
||||
en: 2ba6c43b20a3d347
|
||||
zh: 5647179ddaa38ad9
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/decision:
|
||||
en: e6ffc783859d7509
|
||||
zh: 8678f323985a24f4
|
||||
en: 6c8583bdefc5e36c
|
||||
zh: 0928b4f2876bbfe4
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/alternatives-considered:
|
||||
en: 9ff0511566948d8b
|
||||
zh: 4e1e37b79f63d390
|
||||
en: d6f40bf02df37ab7
|
||||
zh: e5b85ce9d41e8754
|
||||
/agent-note-ship-experimental-capabilities-as-optional-bundles/consequences:
|
||||
en: 9d2b313f15fc9ce0
|
||||
zh: 522341c83565ac33
|
||||
en: 38d27583533cd028
|
||||
zh: 9bf11f5489c1aa43
|
||||
+4
-2
@@ -14,7 +14,7 @@ The Web plugin page offered two optional bundles, Agent Teams and voice input. A
|
||||
|
||||
An optional bundle's dependencies are downloaded with every `dsh` installation. [Reasoning translation](2026-10-05-anonymous-reasoning-translation.md) uses three experimental packages without adding third-party runtime dependencies. Admission therefore considers installation cost and whether a switch supplies a usable composition without an additional configuration form. Browser and computer providers remain explicit compositions because they add substantial runtimes or require an external executable. Stagehand requires model credentials; Python PTC replaces a core provider and conflicts with TypeScript workflow consumers. Demonstrative hooks, webhooks, and mods stay outside the list. [Session Inspector](../feature/2026-09-24-session-inspector.md) owns the Inspector's distinct activation behavior.
|
||||
|
||||
Bundles add scoped rows with the [profile patch operation](../../../../packages/boot/app-boot/README.md): `preset` names a declaration row, and the ordinary patch operates on its child list. Full Web presets receive the optional model tools; minimal retains its current shell and working-directory tools. Search, Ralph, and terminals supply their own isolated services. Search opens one in-memory index on demand per retained preset revision; the Host search policy stays unchanged. Badge skills and title providers act on the Host. Existing Agents retain their preset generation, and later user patch layers retain precedence. Title replacement waits for disposal of the previous provider.
|
||||
Bundles insert ordinary Host rows; optional model tools register in the global tool layer, so every preset, including minimal, receives them. Plugin management, profile composition, and reload need no bundle-specific behavior. Search, Ralph, and terminals supply their own isolated services in a Host group. Search opens one in-memory index on demand; the Host search policy stays unchanged. Badge skills and title providers act on the Host. Switching a bundle registers or removes its tools for live Agents, and later user patch layers retain precedence. A replacement title provider may register once the previous provider's disposal starts; the previous provider's late results never commit.
|
||||
|
||||
Claude Code and Codex install on demand through the separate `ON_DEMAND_BUNDLES` catalog; the [on-demand native-bundle decision](2026-10-05-official-on-demand-bundles.md) owns the cost, version, and offline-discovery trade-offs.
|
||||
|
||||
@@ -26,6 +26,8 @@ Claude Code and Codex install on demand through the separate `ON_DEMAND_BUNDLES`
|
||||
|
||||
**Register preset extensions at runtime.** A separate registration path would need its own ordering, module resolution, generation updates, and inspection behavior. Profile patches keep one effective composition for boot, dumps, validation, and user overrides.
|
||||
|
||||
**Insert rows into named presets with a `preset` patch operation.** Limiting tools to full presets required a second patch language across profile composition, reload, plugin management, and the plugin page, plus retained-generation removal checks. Global tools reach the same Agents through the existing Host composition.
|
||||
|
||||
## Consequences
|
||||
|
||||
Official discovery does not imply experimental status or a different plugin lifecycle. Switching a bundle off keeps its installed files; failed activation retains the existing saved-selection behavior. Scoped rows appear read-only under their bundle, and removal respects modules retained by live preset generations. Individual and combined composition tests cover the supported presets; live switching and recorded Sessions cover activation and model-visible behavior.
|
||||
Official discovery does not imply experimental status or a different plugin lifecycle. Switching a bundle off keeps its installed files; failed activation retains the existing saved-selection behavior. Bundle rows are ordinary Host rows with their usual switches. Individual and combined composition tests cover the Host rows and every preset's tool catalog; live switching and recorded Sessions cover activation and model-visible behavior.
|
||||
+4
-2
@@ -14,7 +14,7 @@ Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto r
|
||||
|
||||
可选 bundle 的依赖随每次 `dsh` 安装下载。[思考过程翻译](2026-10-05-anonymous-reasoning-translation.zh.md)使用三个实验包,不增加第三方运行时依赖。因此准入需要考虑安装成本,以及开关能否在不增加配置表单的情况下提供可用组合。浏览器与电脑提供方仍使用显式组合,因为它们会增加较大的运行时或要求外部可执行文件。Stagehand 需要模型凭据;Python PTC 替换核心提供方,并与 TypeScript workflow 消费方冲突。演示性钩子、webhook 与模组不进入名单。[Session Inspector](../feature/2026-09-24-session-inspector.zh.md) 管理 Inspector 特有的激活行为。
|
||||
|
||||
Bundle 通过 [profile patch 操作](../../../../packages/boot/app-boot/README.zh.md)添加作用域行:`preset` 指定声明行,普通 patch 对其子列表生效。完整 Web 预设获得可选模型工具;minimal 保持当前的 shell 与工作目录工具。搜索、Ralph 与终端提供自己的隔离服务。搜索在每个保留的预设修订中按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。已有 Agent 保留其预设代际,后续用户 patch 层继续优先。标题替换等待前一个提供方完成清理。
|
||||
Bundle 插入普通 Host 行;可选模型工具注册到全局工具层,因此包括 minimal 在内的所有预设都会获得它们。插件管理、profile 组合与重载无需针对 bundle 的特殊行为。搜索、Ralph 与终端在 Host 组内提供自己的隔离服务。搜索按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。切换 bundle 会为运行中的 Agent 注册或移除其工具,后续用户 patch 层继续优先。前一个标题提供方开始清理后,替代提供方即可注册;前一个提供方的迟到结果不会提交。
|
||||
|
||||
Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,其成本、版本与离线发现取舍由[按需原生 bundle 决策](2026-10-05-official-on-demand-bundles.zh.md)管理。
|
||||
|
||||
@@ -26,6 +26,8 @@ Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,
|
||||
|
||||
**在运行时注册预设扩展。** 独立注册路径需要自己的排序、模块解析、代际更新与检查行为。Profile patch 为启动、配置导出、校验和用户覆盖保留同一个有效组合。
|
||||
|
||||
**用 `preset` patch 操作向指定预设插入行。** 只把工具限定在完整预设中,需要在 profile 组合、重载、插件管理和插件页中引入第二套 patch 语言,并为保留代际增加删除检查。全局工具通过现有 Host 组合即可到达同样的 Agent。
|
||||
|
||||
## 后果
|
||||
|
||||
官方发现不表示实验性状态,也不引入不同的插件生命周期。关闭 bundle 保留已安装文件;激活失败保留现有的已保存选择行为。作用域行在 bundle 内只读展示,删除操作尊重 live 预设代际仍持有的模块。独立与组合测试覆盖受支持的预设;实时切换与已记录 Session 覆盖激活和模型可见行为。
|
||||
官方发现不表示实验性状态,也不引入不同的插件生命周期。关闭 bundle 保留已安装文件;激活失败保留现有的已保存选择行为。Bundle 行是普通 Host 行,使用常规开关。独立与组合测试覆盖 Host 行及每个预设的工具目录;实时切换与已记录 Session 覆盖激活和模型可见行为。
|
||||
+4
-4
@@ -9,11 +9,11 @@
|
||||
en: a590bcef6c9760c0
|
||||
zh: 19e6f61c2c7d8d3b
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/decision:
|
||||
en: 31e3c52841f31a11
|
||||
zh: ac2ad6e54ef9e150
|
||||
en: f0fabc0d4fbd1178
|
||||
zh: cf19938a7a7dea93
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/alternatives-considered:
|
||||
en: 42cb0f808a145871
|
||||
zh: 225012615d1ecdf5
|
||||
/agent-note-discover-official-bundles-without-installing-their-runtimes/consequences:
|
||||
en: 899b07d90024e019
|
||||
zh: 01d22ac4b2444feb
|
||||
en: 293286dcd7e3325e
|
||||
zh: c6c15212e85d050b
|
||||
@@ -12,13 +12,13 @@ Native subagent integrations bring their own runtimes. Shipping those dependenci
|
||||
|
||||
`ON_DEMAND_BUNDLES` admits the Claude Code and Codex bundles to an installation-owned catalog. The catalog embeds their package-owned localized metadata and icons, so discovery works offline without resolving or importing either package. Official identifies project maintenance; optional delivery and experimental maturity remain independent properties.
|
||||
|
||||
Selection uses the ordinary third-party bundle installer, profile dependency, scoped patches, compatibility evaluation, progress, cancellation, registry fallback, and build-script approval. Native authentication and configuration remain provider-owned.
|
||||
Selection uses the ordinary third-party bundle installer, profile dependency, Host row patches, compatibility evaluation, progress, cancellation, registry fallback, and build-script approval. Native authentication and configuration remain provider-owned.
|
||||
|
||||
The running installation owns the target. Published applications offer the exact DSH release version and save an exact registry dependency pin. Source Web and development Desktop offer ordinary `link:` dependencies into the same checkout as the running CLI, using its prepared workspace dependencies and peers. The source target follows the actual installation, never the current working directory or a prerelease suffix; an incomplete checkout does not fall back to the registry. A development checkout can share a version string with different published code, so replacing an installed registry copy or another checkout’s link compares the source as well as the version. Replacement is explicit, preserves selection, and reports required restarts; startup does not download providers. [Plugin Manager](../../../../packages/boot/plugin-manager/README.md#use-this-package) owns the operational details.
|
||||
|
||||
Every Official package remains in the DSH release family. Packed DSH dependencies, optional dependencies, and peers use the exact family version. Publication prerequisites place catalog packages before the DSH package advertising them without adding their runtimes to DSH production dependencies. Catalog verification rejects missing metadata, unsupported membership, and stale generated content.
|
||||
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch and preset-generation behavior. Native bundles require the full Web preset targets; custom profiles mount their providers and tools explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bundles.md) still owns lightweight installation-provided bundles. The [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md) owns shared patch behavior and global optional tools. Native bundles insert ordinary Host rows, so any profile can select them; profiles that keep the package without its bundle mount the same rows explicitly. The [identity compatibility decision](2026-10-06-web-desktop-plugin-identity-compatibility.md) owns retained names, selectors, and deferred naming cleanup.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -34,4 +34,4 @@ The [shipped optional-bundle policy](../process/2026-09-15-shipped-optional-bund
|
||||
|
||||
## Consequences
|
||||
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and respects retained preset modules. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
Registry delivery requires a reachable registry; development delivery requires a prepared, built checkout and normal rebuild/restart discipline. Equal source version strings do not certify identical build outputs. Off only deselects; Remove remains separate and refuses while the bundle's rows are in use. Activation failure can retain the saved dependency and selection; there is no rollback. Offline metadata, release pins, source-target selection, same-version source mismatches, clean installed artifacts, and release ordering have dedicated checks. Native-provider authentication is not part of catalog discovery.
|
||||
@@ -12,13 +12,13 @@ Status: implemented
|
||||
|
||||
`ON_DEMAND_BUNDLES` 将 Claude Code 和 Codex bundle 纳入安装方维护的目录。目录内嵌包自身拥有的本地化元信息和图标,因此发现过程离线可用,无需解析或导入这两个包。Official 表示项目维护;可选交付与实验成熟度仍是独立属性。
|
||||
|
||||
选择操作复用普通第三方 bundle 的安装器、profile 依赖、作用域 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。原生认证与配置仍由提供方拥有。
|
||||
选择操作复用普通第三方 bundle 的安装器、profile 依赖、Host 行 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。原生认证与配置仍由提供方拥有。
|
||||
|
||||
当前运行的安装决定目标。已发布应用提供精确的 DSH 发布版本,并保存精确的注册表依赖版本。源码 Web 和开发 Desktop 提供指向当前 CLI 所在 checkout 的普通 `link:` 依赖,使用其中已准备好的工作区依赖和 peer。源码目标跟随实际安装位置,不由当前工作目录或预发布版本后缀决定;不完整的 checkout 不回退到注册表。开发 checkout 可以与内容不同的已发布代码共用版本字符串,因此替换已安装的注册表副本或其他 checkout 的链接时,同时比较来源和版本。替换需显式触发,保留选择状态,并报告是否需要重启;启动时不下载提供方。[插件管理器](../../../../packages/boot/plugin-manager/README.zh.md#use-this-package)拥有操作细节。
|
||||
|
||||
所有 Official 包仍属于 DSH 发布家族。打包后的 DSH 依赖、可选依赖与 peer 均使用家族精确版本。发布前置关系要求先发布目录包,再发布宣传它们的 DSH 包,同时不把这些运行时加入 DSH 生产依赖。目录校验拒绝缺失的元信息、不受支持的成员和过期生成内容。
|
||||
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 与预设代际行为。原生 bundle 要求完整 Web 预设目标存在;自定义 profile 显式挂载提供方和工具。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
[随附可选 bundle 策略](../process/2026-09-15-shipped-optional-bundles.zh.md)仍管理安装随附的轻量 bundle。[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)管理共享 patch 行为与全局可选工具。原生 bundle 插入普通 Host 行,因此任何 profile 都可以选择它们;保留包但不选择其 bundle 的 profile 显式挂载相同的行。[身份兼容决策](2026-10-06-web-desktop-plugin-identity-compatibility.zh.md)管理保留名称、选择器和暂缓的命名清理。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -34,4 +34,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并尊重预设仍保留的模块。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
注册表交付要求注册表可达;开发交付要求准备并构建 checkout,遵循正常的重新构建和重启流程。源码版本字符串相同不证明构建输出相同。关闭仅取消选择;移除保持独立,并在 bundle 的行仍在使用时拒绝执行。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、发布版本锁定、源码目标选择、同版本来源差异、干净安装产物和发布顺序有专门检查。原生提供方认证不属于目录发现过程。
|
||||
+6
-6
@@ -9,14 +9,14 @@
|
||||
en: 8112c1c059d1cf10
|
||||
zh: 852d23c56a93a555
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/decision:
|
||||
en: ac725c281d7dfb41
|
||||
zh: 5adcaa6ca6eab384
|
||||
en: 90d74300f045bf6e
|
||||
zh: 088e88944478ca2a
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/deferred-naming-cleanup:
|
||||
en: 4c428f65ef611d07
|
||||
zh: 1fdc5467b0203d08
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/alternatives-considered:
|
||||
en: 1bf03723fea6c9a4
|
||||
zh: 2d0d2fc997f7c022
|
||||
en: 65af55b5775c9bc5
|
||||
zh: 2ea75b3fd33ac0c2
|
||||
/agent-note-preserve-web-and-desktop-plugin-identities-across-experimental-consolidation/consequences:
|
||||
en: 60f7c823b5d2ed6f
|
||||
zh: 75f375487cab3c56
|
||||
en: e8e96b0354f1a3c6
|
||||
zh: ad1bfe3600e14ff1
|
||||
+4
-4
@@ -12,9 +12,9 @@ Moving a capability into the experimental directory changes its ownership and su
|
||||
|
||||
The moved packages retain the published names declared by the [experimental naming policy](../../../../scripts/experimental-package-policy.ts). That typed directory-to-name map is the sole maintained inventory of naming exceptions. Package manifests, consumers, installation checks, and bundler checks agree with it; a missing, moved, or duplicated exception is rejected. [Experimental status](../../../../packages/experimental/README.md#status) remains independent of spelling, Official discovery, and explicit selection.
|
||||
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid selectors inside their applicable Host or preset scope. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows, and a complete preset replacement remains authoritative. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
Existing badge, Ralph, and native delegation row IDs remain valid Host row selectors. New isolation groups and newly introduced capabilities retain their new IDs. Later user layers still replace configuration or disable rows. Selecting an optional bundle is still explicit: a patch targeting an absent row does not select its bundle.
|
||||
|
||||
Compatibility protection covers Web and Desktop profiles, including existing native provider package identities, selections, configuration, and authentication. Headless, SDK, and ACP compositions may require changes. Native bundles use the ordinary shared patch operation and require the full preset targets; separate provider-only compatibility wrappers are unnecessary for the protected profiles. Public APIs remain pre-stable, and new output metadata stays required unless an actual consumer needs an accommodation.
|
||||
Compatibility protection covers Web and Desktop profiles, including existing native provider package identities, selections, configuration, and authentication. Native bundles insert ordinary Host rows, so Headless, SDK, and ACP compositions can select them too; separate provider-only compatibility wrappers are unnecessary. Public APIs remain pre-stable, and new output metadata stays required unless an actual consumer needs an accommodation.
|
||||
|
||||
## Deferred naming cleanup
|
||||
|
||||
@@ -31,10 +31,10 @@ These naming changes are deferred, not prerequisites for experimental classifica
|
||||
|
||||
**Publish aliases or keep duplicate compatibility rows.** Parallel identities need continuing resolution and removal rules and can register the same capability twice. Retaining the established identity avoids that additional mechanism.
|
||||
|
||||
**Keep provider-only bundles through additional wrapper packages.** Web and Desktop already provide the required presets. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
**Keep provider-only bundles through additional wrapper packages.** Native bundles insert ordinary Host rows that compose into any profile. Extra delivery packages would preserve compositions outside the selected compatibility scope and increase catalog and release maintenance.
|
||||
|
||||
## Consequences
|
||||
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides, disabled state, and preset replacement. No general alias or automatic profile migration mechanism is introduced.
|
||||
The repository carries a bounded naming-exception map and tests that classify retained names through direct imports, aliases, transitive dependencies, and packed or bundled artifacts. An unprefixed name does not grant admission to the default product. Existing row identities have Web/Desktop composition coverage for user overrides and disabled state. No general alias or automatic profile migration mechanism is introduced.
|
||||
|
||||
The [classification decision](../process/2026-10-05-product-package-classification.md), [optional-composition decision](2026-09-21-experimental-capabilities-as-optional-bundles.md), and [on-demand discovery decision](2026-10-05-official-on-demand-bundles.md) retain their separate responsibilities. Released Session generations and persisted hook event names remain protected independently of this profile compatibility scope.
|
||||
+4
-4
@@ -12,9 +12,9 @@ Status: implemented
|
||||
|
||||
迁移的包保留[实验包命名策略](../../../../scripts/experimental-package-policy.ts)声明的已发布名称。这个带类型的目录到包名映射是命名例外唯一手工维护的清单。包清单、消费者、安装检查和打包检查与其保持一致;缺失、迁移到其他位置或重复归属的例外会被拒绝。[实验状态](../../../../packages/experimental/README.zh.md#status)仍独立于名称拼写、Official 发现和显式选择。
|
||||
|
||||
既有徽章、Ralph 和原生委派行 ID 在对应的 Host 或预设作用域中保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行,完整预设替换仍具有决定权。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
既有徽章、Ralph 和原生委派行 ID 作为 Host 行选择器保持有效。新增隔离组和新引入的能力保留各自的新 ID。后续用户层仍可替换配置或禁用行。可选 bundle 仍须显式选择:指向缺失行的 patch 不会自动选择其 bundle。
|
||||
|
||||
兼容性保护覆盖 Web 和 Desktop profile,包括既有原生提供方的包身份、选择状态、配置和认证。Headless、SDK 和 ACP 组合可以要求修改。原生 bundle 使用普通的共享 patch 操作并要求完整预设目标存在;受保护的 profile 不需要单独的仅提供方兼容包装包。公共 API 仍处于稳定前阶段,新增输出元信息保持必填,除非实际消费者需要适配。
|
||||
兼容性保护覆盖 Web 和 Desktop profile,包括既有原生提供方的包身份、选择状态、配置和认证。原生 bundle 插入普通 Host 行,因此 Headless、SDK 和 ACP 组合也可以选择它们;不需要单独的仅提供方兼容包装包。公共 API 仍处于稳定前阶段,新增输出元信息保持必填,除非实际消费者需要适配。
|
||||
|
||||
## 暂缓的命名清理
|
||||
|
||||
@@ -31,10 +31,10 @@ Status: implemented
|
||||
|
||||
**发布别名或保留重复兼容行。** 并行身份需要持续维护解析和移除规则,还可能重复注册同一能力。保留既有身份可以避免增加这种机制。
|
||||
|
||||
**通过额外包装包保留仅提供方 bundle。** Web 和 Desktop 已提供所需预设。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
**通过额外包装包保留仅提供方 bundle。** 原生 bundle 插入普通 Host 行,可组合到任何 profile。额外交付包会保留选定兼容范围之外的组合,并增加目录和发布维护成本。
|
||||
|
||||
## 后果
|
||||
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置、禁用状态和预设替换的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
仓库维护范围明确的命名例外映射,并测试通过直接导入、别名、传递依赖及打包产物识别这些保留名称。没有实验前缀的名称不因此获准进入默认产品。既有行身份具有覆盖用户配置和禁用状态的 Web/Desktop 组合测试。不引入通用别名或自动 profile 迁移机制。
|
||||
|
||||
[分类决策](../process/2026-10-05-product-package-classification.zh.md)、[可选组合决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)和[按需发现决策](2026-10-05-official-on-demand-bundles.zh.md)继续承担各自独立的职责。已发布 Session 代际和持久化 hook 事件名的保护独立于此 profile 兼容范围。
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-10-08-desktop-shell-configuration.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host:
|
||||
en: 860c8afa57227b89
|
||||
zh: bec6a19319050aed
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/problem:
|
||||
en: 9f652292b62cd015
|
||||
zh: 14a97b5a4884c008
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/decision:
|
||||
en: e58311d3d5ebb6b6
|
||||
zh: b4c791fcfde7b986
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/alternatives-considered:
|
||||
en: 349210f3cf5f168b
|
||||
zh: 019acfc3c3d71b4b
|
||||
/agent-note-desktop-shell-configuration-independent-of-the-host/consequences:
|
||||
en: a523f43819051d4b
|
||||
zh: 69926bc3761195ea
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Desktop shell configuration independent of the Host
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-10-08-desktop-shell-configuration.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Desktop updates and native recovery can run before the Host starts or while it is unavailable. Depending on Host configuration prevents these functions from resolving their preferences when needed. A shared settings page does not identify which process owns each preference.
|
||||
|
||||
## Decision
|
||||
|
||||
Electron main owns `userData/desktop/settings.json` independently of Host startup and Cordis evaluation. The test authentication popup switch uses this file; the [Desktop reference](../../../../apps/desktop/README.md#local-desktop-settings) owns its fields and behavior.
|
||||
|
||||
Ownership follows runtime dependencies: Electron-owned functions needing configuration before Host startup or while the Host is unavailable use shell configuration. Host plugin settings, including shared Web/Desktop preferences, continue to use Cordis. Displaying a setting in Desktop alone does not justify shell ownership. A unified settings interface can use separate storage owners.
|
||||
|
||||
The `desktop` subdirectory groups shell configuration without relocating existing files. The [shortcut decision](2026-09-20-device-local-shortcut-preferences.md) retains ownership of `userData/keybindings.json` and its persistence semantics. The [shared Web application decision](2026-09-10-desktop-web-wrapper.md) still governs Host composition and profile configuration.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Put every preference in Cordis.** This requires a working Host. Reading `cordis.patch.yml` directly in Electron duplicates configuration evaluation; a patch alone omits plugin defaults and other configuration layers.
|
||||
|
||||
**Move every setting shown in Desktop.** Interface placement does not imply a Host-independent requirement. This splits shared Web/Desktop preferences and moves configuration away from its consuming plugins.
|
||||
|
||||
## Consequences
|
||||
|
||||
Shell and Host configuration have separate owners. Each setting needs documented ownership and effective timing. Independent shell configuration supports additional Host-independent functions without introducing a second application backend.
|
||||
|
||||
Existing settings are candidates for individual evaluation when their functionality changes or a Host-independent use is required. This is an evaluation direction, not a scheduled migration or predefined list. A migration must identify a single authoritative source, transfer existing values and define when changes take effect, without ambiguous precedence between two stores. Shared Host-managed settings remain in Cordis unless their ownership requirements change.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Agent Note: Desktop 壳层配置独立于 Host
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-10-08-desktop-shell-configuration.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
Desktop 更新和原生恢复可以在 Host 启动前或不可用时运行。依赖 Host 配置会使这些功能无法在需要时解析偏好。统一设置页面不能决定每项偏好由哪个进程负责。
|
||||
|
||||
## Decision
|
||||
|
||||
Electron 主进程负责 `userData/desktop/settings.json`,不依赖 Host 启动或 Cordis 求值。测试鉴权弹窗开关使用此文件;[Desktop 参考文档](../../../../apps/desktop/README.zh.md#local-desktop-settings)统一说明字段和行为。
|
||||
|
||||
配置归属按运行依赖划分:由 Electron 负责且需要在 Host 启动前或不可用时读取配置的功能,使用壳层配置。Host 插件设置,包括 Web/Desktop 共用偏好,继续使用 Cordis。仅仅展示在 Desktop 中,不构成归属壳层的理由。统一设置界面可以使用不同的存储归属。
|
||||
|
||||
`desktop` 子目录集中存放壳层配置,不迁移已有文件。[快捷键决策](2026-09-20-device-local-shortcut-preferences.zh.md)继续负责 `userData/keybindings.json` 及其持久化语义。[共享 Web 应用决策](2026-09-10-desktop-web-wrapper.zh.md)仍约束 Host 组合和 profile 配置。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**所有偏好都放入 Cordis。** 这需要可用的 Host。由 Electron 直接读取 `cordis.patch.yml` 会重复实现配置求值;单独的 patch 不包含插件默认值和其他配置层。
|
||||
|
||||
**迁移所有在 Desktop 展示的设置。** 界面位置不意味着需要独立于 Host。这会拆分 Web/Desktop 共用偏好,并让配置脱离实际使用它的插件。
|
||||
|
||||
## Consequences
|
||||
|
||||
壳层与 Host 配置分别有自己的归属。每项设置需说明归属和生效时机。独立壳层配置支持更多不依赖 Host 的功能,但不引入第二套应用后端。
|
||||
|
||||
现有设置在功能调整或需要独立于 Host 使用时,可逐项评估。这是评估方向,不是已排期的迁移计划或预设清单。实际迁移需明确唯一配置来源、迁移已有值并定义生效时机,避免两套存储间存在含糊的优先级。共用的 Host 设置在归属要求不变时继续使用 Cordis。
|
||||
@@ -18,11 +18,11 @@
|
||||
en: 83e446c928a20c47
|
||||
zh: b0a2bd24419dd815
|
||||
/agent-note-durable-agent-teams-over-continuable-children/provisioning-and-recovery:
|
||||
en: 085846d0078c0898
|
||||
zh: 40b2ca3ae6864451
|
||||
en: 4345ac9a6ac7ad04
|
||||
zh: 1d19ecbb128a75a3
|
||||
/agent-note-durable-agent-teams-over-continuable-children/mailbox-and-task-transactions:
|
||||
en: ab2ad55021eb34e0
|
||||
zh: 2d1996df7a69201d
|
||||
en: bfc3063536837c0e
|
||||
zh: 6b8e69ba85cc2bb1
|
||||
/agent-note-durable-agent-teams-over-continuable-children/shared-checkout-boundary:
|
||||
en: 34920da68b5763e0
|
||||
zh: 7604392594e4b8a8
|
||||
@@ -30,11 +30,11 @@
|
||||
en: e6348054f024725b
|
||||
zh: 8740636e99940308
|
||||
/agent-note-durable-agent-teams-over-continuable-children/alternatives-considered:
|
||||
en: bb266fe7a97f0828
|
||||
zh: 7c86c2d6d11ba02a
|
||||
en: 8cb2fd8839f5f8b7
|
||||
zh: e302ca03e569de65
|
||||
/agent-note-durable-agent-teams-over-continuable-children/testing:
|
||||
en: 35ce199351d8b63d
|
||||
zh: 332f0520e27e3309
|
||||
en: 092ac3c2b6f552aa
|
||||
zh: c688c1bec5ad8a07
|
||||
/agent-note-durable-agent-teams-over-continuable-children/consequences:
|
||||
en: c3740ae56809e537
|
||||
zh: 76a1e77d14cb6f26
|
||||
en: da9cd22b1185a793
|
||||
zh: e20d7ef9226f237f
|
||||
@@ -30,7 +30,7 @@ The `spawn_teammate` tool prefixes the initial task with a user-role `<system-re
|
||||
|
||||
## Provisioning and recovery
|
||||
|
||||
Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and mailbox-dispatch transactions, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.
|
||||
Creation first appends and flushes a `team/member` provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and send operations, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.
|
||||
|
||||
A root recovery reconciles an unterminated provisioning record against the child's independently persisted Session. Matching direct-parent and continuable descriptors plus a recorded initial user message prove successful admission and produce active; absence, corruption, mismatched provider/lineage, or a missing admitted message produces failed. The creator re-reads the terminal phase under the same Lead-log serializer; if recovery marked failed while creation succeeded, it drains the child and reports a provisioning conflict instead of retaining an orphan. This avoids reconstructing an initial prompt that was never retained in the Team log and contains plugin-reload races.
|
||||
|
||||
@@ -38,11 +38,11 @@ Fresh children have no inherited conversation. Fork children capture the Lead's
|
||||
|
||||
## Mailbox and task transactions
|
||||
|
||||
Peer communication is a Lead-log mailbox. `team/message/queued` is appended and flushed before delivery. The target message carries the stable message id and sender identity in both durable source metadata and a short model-visible prefix. A target receipt is acknowledged with `team/message/delivered` only after its pending inbox item or recorded user message is flushed. Immediate admission is serialized per target in queued-log order, recovery retries queued-minus-delivered in the same order, and delivery folds live or persisted target inbox/history state before cold resume. Every current-version Team payload is runtime-validated before entering replay state. The Team runtime tracks dispatch and asynchronous acknowledgement work from synchronous admission until settlement; disposal closes admission and awaits both before removing the service. Current waiters wake only after the owning Team event flush succeeds.
|
||||
The historical Lead-log mailbox remains a readable format: queued and delivered records keep their stable identity and sender, and Team payload validation still applies to them. Team no longer delivers or acknowledges them; new sends follow the [direct inbox decision](../simplification/2026-09-26-team-direct-inbox.md).
|
||||
|
||||
Unknown mailbox content stays as decoded JSON during event projection and checkpoint admission. Validation inspects its type without rebuilding its fields, because generic object parsing can omit a valid own `__proto__` key. Locally declared content variants undergo structural validation; opaque plugin fields do not gain Team semantics. Cache invalidation is necessary because parser correction alone cannot recover keys omitted from cached state. The [Team documentation](../../../../packages/experimental/agent-team/README.md) owns the restore behavior.
|
||||
|
||||
`send_message` always attempts Steer delivery. A running target receives the message at the nearest step boundary; an inactive target starts a turn if loaded or cold-resumes otherwise. Success means the message is already durable even when temporary delivery failure leaves it queued. The mechanism provides process-local retry and target-Session de-duplication, not a cross-process exactly-once claim. The [Team Steer messaging decision](../../archived/simplification/2026-08-30-team-send-message-steer.md) owns the single-tool scheduling rationale.
|
||||
`send_message` uses Steer for running, idle, and cold targets. New sends return inbox acceptance. The [Team Steer messaging decision](../../archived/simplification/2026-08-30-team-send-message-steer.md) records the single-tool scheduling rationale.
|
||||
|
||||
Shared tasks are complete snapshots with Team-local ids and monotonic revisions. Every mutation carries `expectedRevision`. Any member creates, reads, or claims a ready unowned task; the owner or Lead edits and transitions it, while only the Lead assigns another member. Numeric task ids remain within the safe-integer allocation range, and exhaustion fails without reusing an id. Dependencies must name non-deleted tasks and form a complete DAG. Deleted tasks are retained tombstones. `writeScopes` are normalized path prefixes that produce overlap diagnostics but never block claim or authorize a write.
|
||||
|
||||
@@ -74,20 +74,20 @@ Publication requires `apply` to return a new state object per applied Team event
|
||||
|
||||
**Enable Teams in the default catalog.** Rejected because scoped Team controls would shadow same-named legacy globals and unsolicited delegation would add latency and token cost to simple tasks. An opt-in profile bundle inserts Team and disables the legacy controls without adding Team packages to shipped dependency graphs.
|
||||
|
||||
**Use an in-memory board and mailbox.** Rejected because child settlement, HMR, and process interruption would lose accepted coordination state and make retries ambiguous.
|
||||
**Use an in-memory board and mailbox.** Roster and task state must survive settlement, HMR, and interruption. Historical mailbox records stay readable; message ownership follows the target inbox.
|
||||
|
||||
**Return Team tool results as untyped JSON.** Rejected because an undeclared result type lets `execute` drift from the value the model is promised without a compiler error, and it invites indentation that costs tokens on every roster, task, and receipt. Each Team tool therefore declares its complete result schema and one shared helper renders it compactly.
|
||||
|
||||
## Testing
|
||||
|
||||
Package tests cover identity, name and authority checks, provider selection, reserved-id persistence collisions, child-before-Lead flush ordering, durable provisioning failure and pending-inbox JSONL reconciliation, concurrent target-local ordering, pending/history de-duplication, mailbox limits, post-flush notification, bounded disposal with in-flight creation and dispatch cancellation, failed-member cleanup, task CAS and DAG validation, write-scope warnings, wait cancellation/timeout, inbox-preserving interruption, ordinary-fork isolation, legacy-control shadowing, compact declared-schema result rendering, and scoped registration HMR at per-file 100% coverage. A keyless product snapshot loads the Agent Teams profile bundle through `dsh --profile headless` and pins its complete model-visible tool list, Team policy, and durable workflow projection for two teammates, dependent tasks, peer delivery, waiting, completion, and aggregation. A CLI e2e reuses the same deterministic adapter and verifies normal process exit with persisted Team and child logs.
|
||||
Package tests cover Team authority, provisioning durability, task transitions, direct inbox acceptance, sender attribution, lifecycle cancellation, and historical mailbox readability. The keyless Team profile snapshot pins the policy, tools, and workflow; the CLI composition test checks persisted Team and child logs.
|
||||
|
||||
Model-facing member identities and availability follow the [tool projection reference](../../../../packages/experimental/tool-agent-team/README.md); service residency and durable identity remain distinct.
|
||||
|
||||
## Consequences
|
||||
|
||||
The Lead Session grows with whole task/member snapshots and mailbox acknowledgements. This favors independently inspectable recovery over compact deltas; configured task and pending-mail bounds cap active state, while deleted and delivered history remains append-only until broader Session retention applies.
|
||||
The Lead Session retains whole task/member snapshots and historical mailbox records. This favors inspectable recovery over compact deltas; deleted and delivered history remains append-only until broader Session retention applies.
|
||||
|
||||
An active roster member can be non-resident, so `inactive` is not failure and a send can incur cold-resume latency. Temporary inspection, resume, or inbox-admission failure can leave a durable message queued for recovery. A failed member permanently consumes its name and member slot, making provisioning failures visible instead of silently recycling identity.
|
||||
An active roster member can be non-resident, so `inactive` is not failure and sends can incur cold-resume latency. Admission failures reject new sends. Historical pending mail is never delivered. Failed provisioning permanently consumes its name and member slot.
|
||||
|
||||
Coordination reduces likely checkout conflicts but cannot eliminate writes outside filesystem compare-and-set tools. The final diff and tests remain the Lead's integration boundary.
|
||||
@@ -30,7 +30,7 @@ Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍
|
||||
|
||||
## Provisioning and recovery
|
||||
|
||||
创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与 mailbox dispatch 事务,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。
|
||||
创建操作先在 Lead Session 中追加并 flush `team/member` provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与发送操作,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。
|
||||
|
||||
Root 恢复时会把未终结 provisioning 记录与独立持久 child Session 对账。直接 parent 与 continuable descriptor 匹配,并且已经记录初始用户消息,才能证明准入成功并转为 active;缺失、损坏、provider/lineage 不匹配或缺少已准入消息都会转为 failed。creator 会在同一 Lead 日志 serializer 内重读终态;如果 recovery 在创建成功时先标记 failed,creator 会 drain child 并报告 provisioning conflict,而不是遗留孤儿。这样既无需重建从未保存在 Team 日志中的初始 prompt,也能约束插件 reload 竞争。
|
||||
|
||||
@@ -38,11 +38,11 @@ fresh child 不继承对话。fork child 只捕获一次 Lead 已完成 turn 前
|
||||
|
||||
## Mailbox and task transactions
|
||||
|
||||
Peer 通讯使用 Lead 日志 mailbox。投递前先追加并 flush `team/message/queued`。target message 会在持久 source metadata 与短模型可见前缀中同时携带稳定 message id 和 sender identity。只有 pending inbox 条目或已记录用户消息完成 flush,Lead 日志才写入 `team/message/delivered` acknowledgement。即时准入按 target 和 queued 日志顺序串行化,恢复按同一顺序重试 queued-minus-delivered,并在冷恢复前折叠 live 或 persisted target 的 inbox/历史状态。每个当前版本 Team payload 都会经过运行时验证后才进入 replay state。Team runtime 从同步准入到 settlement 全程跟踪 dispatch 与异步 acknowledgement 工作;dispose 会关闭准入,并在移除服务前等待两者。当前 waiter 只在所属 Team event flush 成功后被唤醒。
|
||||
历史 Lead-log mailbox 仍是可读格式:queued 与 delivered 记录保留稳定身份与发送者,Team payload 校验仍适用于它们。Team 不再投递或确认这些记录;新发送遵循[直接 inbox 决策](../simplification/2026-09-26-team-direct-inbox.zh.md)。
|
||||
|
||||
事件投影和 checkpoint 准入期间,未知 mailbox 内容保持为已解码 JSON。校验只检查其 type,不重建字段,因为通用对象解析可能省略有效的自有 `__proto__` 键。本地声明的内容变体接受结构校验;不透明的插件字段不获得 Team 语义。缓存失效是必要的,因为只修正解析器无法恢复缓存状态中省略的键。[Team 文档](../../../../packages/experimental/agent-team/README.zh.md)拥有恢复行为的说明。
|
||||
|
||||
`send_message` 始终尝试 Steer 投递。running target 在最近的步骤边界收到消息,inactive target 在已加载时启动一个轮次,否则冷恢复。即使临时投递失败让消息保持 queued,成功也表示消息已经持久化。该机制提供进程内重试与 target Session 去重,不宣称跨进程 exactly-once。[Team Steer 消息决策](../../archived/simplification/2026-08-30-team-send-message-steer.md)负责单工具调度的理由。
|
||||
`send_message` 对 running、idle 和 cold target 使用 Steer。新发送返回 inbox 接收结果。[Team Steer 消息决策](../../archived/simplification/2026-08-30-team-send-message-steer.md)记录单一工具调度的理由。
|
||||
|
||||
共享 task 是带 Team-local id 与单调 revision 的完整快照。每次变更都携带 `expectedRevision`。任意 member 可以创建、读取或 claim ready 且无 owner 的任务;Owner 或 Lead 可以编辑和转换;只有 Lead 可以分配给另一个 member。数字 task id 保持在安全整数分配范围内;该范围耗尽时会失败,不会复用 id。依赖必须指向未删除任务,并形成完整 DAG。删除任务保留为 tombstone。`writeScopes` 是规范化路径前缀,只产生重叠诊断,绝不会阻止 claim 或授予写权限。
|
||||
|
||||
@@ -74,20 +74,20 @@ Web panel 读取 Lead Session 的 `agentTeam` 传输投影,因为现有投影
|
||||
|
||||
**在默认工具目录中启用 Team。** 拒绝,因为 scoped Team control 会覆盖同名旧全局工具,主动 delegation 也会给简单任务增加延迟和 token 成本。opt-in profile bundle 会插入 Team 并禁用旧 control,同时不向随附依赖图添加 Team 包。
|
||||
|
||||
**使用内存 task board 与 mailbox。** 拒绝,因为 child settlement、HMR 与进程中断会丢失已接受协调状态,并让重试变得含糊。
|
||||
**使用内存 board 与 mailbox。** roster 与任务状态必须经受 settlement、HMR 与中断。历史 mailbox 记录保持可读;消息归属目标 inbox。
|
||||
|
||||
**让 Team 工具返回未类型化 JSON。** 拒绝,因为未声明的结果类型会让 `execute` 在没有编译错误的情况下偏离对模型的承诺,也会引入在每份 roster、task 与回执上都消耗 token 的缩进。因此每个 Team 工具都声明完整的结果 schema,并由一个共享 helper 紧凑渲染。
|
||||
|
||||
## Testing
|
||||
|
||||
Package test 以逐文件 100% coverage 覆盖身份、名字与权限检查、provider 选择、预留 id 持久化冲突、child-before-Lead flush 顺序、持久 provisioning 失败与 pending-inbox JSONL 对账、target-local 并发顺序、pending/history 去重、mailbox 限额、flush 后 notification、取消在途创建与 dispatch 的有界 dispose、failed member cleanup、task CAS 与 DAG 校验、write-scope warning、wait cancel/timeout、保留 inbox 的 interrupt、普通 fork 隔离、旧 control shadowing、声明 schema 的紧凑结果渲染与 scoped registration HMR。一条 keyless 产品快照会通过 `dsh --profile headless` 加载 Agent Teams profile bundle,并为两个 teammate、依赖任务、peer 投递、等待、完成和汇总固定完整的面向模型工具列表、Team policy 与持久 workflow 投影。CLI e2e 会复用同一个确定性 adapter,并验证带持久 Team 与 child 日志的正常退出。
|
||||
包测试覆盖 Team 权限、provisioning 持久性、任务转换、直接 inbox 接收、发送者归属、生命周期取消和历史 mailbox 可读性。无密钥 Team profile 快照固定 policy、工具与工作流;CLI 组合测试检查持久 Team 和 child 日志。
|
||||
|
||||
模型可见的成员身份与可用状态遵循[工具投影说明](../../../../packages/experimental/tool-agent-team/README.zh.md);服务驻留状态和持久身份仍保持区分。
|
||||
|
||||
## Consequences
|
||||
|
||||
Lead Session 会随着完整 task/member 快照与 mailbox acknowledgement 增长。该设计用可独立检查的恢复能力换取更紧凑的 delta;配置的 task 与 pending-mail 限额限制 active state,而 deleted 与 delivered 历史会保持 append-only,直到更广泛的 Session retention 生效。
|
||||
Lead Session 保留完整 task/member 快照和历史 mailbox 记录。这优先支持可检查的恢复,而不是紧凑增量;deleted 与 delivered 历史在更广泛的 Session 保留策略生效前仍只追加。
|
||||
|
||||
active roster member 可以不驻留,因此 `inactive` 不表示失败,send 可能产生 cold-resume 延迟。临时 inspection、resume 或 inbox 准入失败可能留下持久 queued 消息等待恢复。failed member 会永久占用名字与 member slot,使 provisioning failure 保持可见而不是静默回收身份。
|
||||
active roster 成员可能不驻留,因此 `inactive` 不表示失败,发送可能产生冷恢复延迟。准入失败会拒绝新发送。历史 pending 邮件永远不会被投递。provisioning 失败永久占用其名称和成员槽位。
|
||||
|
||||
协调可以降低 checkout 冲突概率,但无法消除文件系统 CAS 工具之外的写入。最终 diff 与测试仍是 Lead 的集成边界。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bilingual-pair consistency record for 2026-09-26-team-direct-inbox.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-09-26-team-direct-inbox.md
|
||||
/agent-note-team-messages-use-the-target-inbox:
|
||||
en: ae17b789652784b5
|
||||
zh: fa462ace95c8cdaa
|
||||
/agent-note-team-messages-use-the-target-inbox/problem:
|
||||
en: 83296699f94c77d3
|
||||
zh: e681a426bf0a703a
|
||||
/agent-note-team-messages-use-the-target-inbox/decision:
|
||||
en: 8d5fed34f19c7302
|
||||
zh: 25417793d3797775
|
||||
/agent-note-team-messages-use-the-target-inbox/alternatives-considered:
|
||||
en: e847556d8e9f1071
|
||||
zh: bf1abe2f0e201eac
|
||||
/agent-note-team-messages-use-the-target-inbox/consequences:
|
||||
en: 24085acb09438dc8
|
||||
zh: 27c92e8de8cf7dbe
|
||||
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Team messages use the target inbox
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-26-team-direct-inbox.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
A second Team outbox duplicates Agent inbox ownership with separate identities, acknowledgements, persistence checkpoints, and retry ordering. Callers need an explicit admission result while the continuation owner already provides target locking, lifecycle authorization, and cold recovery.
|
||||
|
||||
## Decision
|
||||
|
||||
New Team sends return the accepted inbox `MessageId` or throw. They use live Lead Steer or the host-only subagent Steer adapter, retaining the actual sender through the existing `agent-message` source and the member name in content. Acceptance follows ordinary Agent persistence; it neither confirms model processing nor adds a synchronous flush guarantee. Team does not retain unaccepted intent, retry it after restart, or deduplicate explicit resends.
|
||||
|
||||
Historical mailbox records stay decodable and projected so existing Lead logs replay, but Team never delivers or acknowledges them. The package is experimental with no stability promise, and a second dispatch path with receipt inspection, per-target ordering, and its own disposal wait costs more than the undelivered old messages it would recover. This partially supersedes the mailbox decision in [Agent Teams](../feature/2026-08-05-agent-teams.md); roster and task decisions remain there. The [package reference](../../../../packages/experimental/agent-team/README.md#understand-the-implementation) owns the resulting limitation.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the Team outbox for all sends.** This preserves a stronger retry promise but duplicates coordination and requires maintaining two acceptance states. Reintroduce it only for a concrete durable send-intent requirement that the Agent inbox cannot serve.
|
||||
|
||||
**Expand public sibling messaging permissions.** Team membership is separate from subagent parent authority. The host adapter authorizes the exact Lead without misattributing the sender or broadening public child controls.
|
||||
|
||||
**Drain historical pending items after restart.** This honors old accepted intent, but it keeps a second delivery owner beside the Agent inbox for data that only an experimental opt-in composition wrote. Removing the decoder as well is also rejected, because Lead logs containing mailbox records would then fail to replay.
|
||||
|
||||
## Consequences
|
||||
|
||||
New attempts have one identity and one inbox owner. A failed attempt is visible to its caller and has no Team retry promise. Historical logs still cost decoder and projection code. A historical message that its target had not recorded before the upgrade is never delivered. Tests cover direct Lead/peer acceptance, cold recovery, sender attribution, failures, cancellation, disposal, and that historical queued mail stays readable and undelivered after restart.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Agent Note: Team 消息使用目标 inbox
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-26-team-direct-inbox.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
第二套 Team outbox 通过独立身份、确认、持久化检查点和重试排序重复了 Agent inbox 的职责。调用方需要明确的准入结果,而 continuation owner 已提供目标锁、生命周期授权和冷恢复。
|
||||
|
||||
## 决策
|
||||
|
||||
新的 Team 发送返回获准 inbox 的 `MessageId` 或抛出错误。它们使用 live Lead Steer 或 host-only subagent Steer adapter,通过现有 `agent-message` source 保留真实发送者,并在正文中保留成员名称。接收遵循普通 Agent 的持久化策略,既不确认模型已处理,也不增加同步 flush 保证。Team 不保留尚未接收的发送意图,不在重启后重试,也不对显式重发去重。
|
||||
|
||||
历史 mailbox 记录继续可解码并参与投影,使现有 Lead 日志能够回放,但 Team 不再投递或确认它们。该包是实验性的,没有稳定性承诺;另设一条带回执检查、同目标排序和独立 dispose 等待的投递路径,其代价高于它能补回的未投递旧消息。这部分取代了 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的 mailbox 决策;roster 和任务决策仍由原记录负责。[包参考](../../../../packages/experimental/agent-team/README.zh.md#understand-the-implementation)定义由此产生的限制。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**为所有发送保留 Team outbox。** 这保留了更强的重试承诺,但重复协调职责,并要求维护两个接收状态。只有具体的持久发送意图需求无法由 Agent inbox 满足时,才重新引入它。
|
||||
|
||||
**扩大公开的 sibling 消息权限。** Team 成员关系与 subagent parent 权限不同。host adapter 授权确切的 Lead,不会错误归属发送者,也不会扩大公开 child control 权限。
|
||||
|
||||
**重启后补送历史 pending 项。** 这能兑现已接收的旧意图,但会在 Agent inbox 之外保留第二个投递 owner,而这些数据只由实验性的可选组合写入。同时删除 decoder 的方案也被否决,因为包含 mailbox 记录的 Lead 日志将无法回放。
|
||||
|
||||
## 后果
|
||||
|
||||
新尝试只有一个身份和一个 inbox owner。失败尝试会向调用方报告,且没有 Team 重试承诺。历史日志仍需要 decoder 与投影代码。升级前目标尚未记录的历史消息永远不会被投递。测试覆盖直接 Lead/peer 接收、冷恢复、发送者归属、失败、取消、dispose(资源释放),以及重启后历史排队消息仍可读取且不被投递。
|
||||
@@ -17,7 +17,7 @@ function records(content: string): Record<string, unknown>[] {
|
||||
}
|
||||
|
||||
describe('dsh run with Agent Teams enabled', () => {
|
||||
it('runs two teammates, durable peer mail, dependent tasks, waiting, and final aggregation', async () => {
|
||||
it('runs two teammates, direct peer messages, dependent tasks, waiting, and final aggregation', async () => {
|
||||
const cwd = await mkdtemp(join(tmpdir(), 'dsh-agent-team-headless-'))
|
||||
try {
|
||||
const home = join(cwd, '.dsh')
|
||||
@@ -103,8 +103,11 @@ describe('dsh run with Agent Teams enabled', () => {
|
||||
expect(root).toBeDefined()
|
||||
const eventTypes = root!.map(record => record.type)
|
||||
expect(eventTypes.filter(type => type === 'team/member')).toHaveLength(4)
|
||||
expect(eventTypes).toContain('team/message/queued')
|
||||
expect(eventTypes).toContain('team/message/delivered')
|
||||
expect(eventTypes).not.toContain('team/message/queued')
|
||||
expect(eventTypes).not.toContain('team/message/delivered')
|
||||
const peerMessages = parsed.flatMap(log => log.filter(record => record.type === 'user/message'
|
||||
&& (record.data as { source: { kind: string } }).source.kind === 'agent-message'))
|
||||
expect(peerMessages).toHaveLength(2)
|
||||
const taskEvents = root!.filter(record => record.type === 'team/task')
|
||||
expect(taskEvents.filter((record) => {
|
||||
const data = record.data as { task?: { status?: string } } | undefined
|
||||
|
||||
@@ -445,13 +445,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"not": {
|
||||
"required": [
|
||||
"preset"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -501,13 +494,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"not": {
|
||||
"required": [
|
||||
"preset"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -557,13 +543,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"not": {
|
||||
"required": [
|
||||
"preset"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -599,11 +578,6 @@
|
||||
"properties": {
|
||||
"insert": {
|
||||
"$ref": "#/$defs/entryList"
|
||||
},
|
||||
"preset": {
|
||||
"type": "string",
|
||||
"pattern": "\\S",
|
||||
"description": "Outer preset row id. The remaining patch fields apply to its current config.plugins list; a missing or unaddressable preset fails composition."
|
||||
}
|
||||
},
|
||||
"description": "An insert appends entries, optionally inside the group identified by id. Other patches replace supplied fields; config is replaced wholesale, not deep-merged. A truthy name asserts the existing plugin name rather than renaming it. Unknown targets and non-insert patches without a nonempty id are warned and skipped."
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/** Scope-owned cancellation during shutdown of the shipped Web profile. */
|
||||
import { existsSync, writeFileSync } from 'node:fs'
|
||||
import { setImmediate } from 'node:timers/promises'
|
||||
import { createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm'
|
||||
|
||||
export const name = 'agent-shutdown-fixture'
|
||||
export const inject = ['agents', 'llm', 'appReady', 'appExit']
|
||||
|
||||
class Adapter extends LlmAdapter {
|
||||
async resolveModel(provider, model) {
|
||||
return { provider, id: model, name: model }
|
||||
}
|
||||
|
||||
async *stream() {
|
||||
throw new Error('The cancelled request must not reach the adapter.')
|
||||
}
|
||||
}
|
||||
|
||||
/** Hold one turn until cancellation and report the state seen by scope cleanup. */
|
||||
export function apply(ctx, config) {
|
||||
ctx.llm.registerAdapter(['shutdown-fixture'], new Adapter())
|
||||
const exit = ctx.appExit
|
||||
ctx.effect(() => ctx.appReady.onReady(() => {
|
||||
void start(ctx, config).catch(error => { console.error(error); exit(1) })
|
||||
}))
|
||||
if (process.platform === 'win32') {
|
||||
const timer = setInterval(() => {
|
||||
if (!existsSync(config.interrupt)) return
|
||||
clearInterval(timer)
|
||||
process.emit('SIGTERM')
|
||||
}, 20)
|
||||
ctx.effect(() => () => { clearInterval(timer) })
|
||||
}
|
||||
}
|
||||
|
||||
async function start(ctx, config) {
|
||||
const { agent } = await ctx.agents.create({
|
||||
sessionId: 'shutdown-fixture',
|
||||
agentOptions: { provider: 'shutdown-fixture', model: 'shutdown-fixture' },
|
||||
meta: { cwd: config.root },
|
||||
})
|
||||
const controller = new AbortController()
|
||||
let abortHandled = 0
|
||||
controller.signal.addEventListener('abort', () => {
|
||||
agent.cancel({ kind: 'parent' })
|
||||
agent.cancel({ kind: 'parent' })
|
||||
abortHandled++
|
||||
}, { once: true })
|
||||
agent.ctx.effect(() => () => {
|
||||
controller.abort()
|
||||
controller.abort()
|
||||
const events = agent.session.snapshotEvents()
|
||||
writeFileSync(config.result, JSON.stringify({
|
||||
abortHandled,
|
||||
status: agent.status,
|
||||
nextStep: agent.inbox.nextStep,
|
||||
nextTurn: agent.inbox.nextTurn,
|
||||
lastEvent: events.at(-1),
|
||||
cancelled: events.filter(event => event.type === 'agent/inbox/spliced' && event.data.outcome === 'canceled'),
|
||||
}))
|
||||
})
|
||||
const message = text => createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } })
|
||||
if (config.running) {
|
||||
const entered = Promise.withResolvers()
|
||||
agent.ctx.on('agent/request', async ({ signal }, next) => {
|
||||
const aborted = Promise.withResolvers()
|
||||
signal.addEventListener('abort', () => { aborted.resolve() }, { once: true })
|
||||
entered.resolve()
|
||||
await aborted.promise
|
||||
await setImmediate()
|
||||
return next()
|
||||
})
|
||||
agent.followup(message('active'))
|
||||
await entered.promise
|
||||
}
|
||||
agent.send(message('pending step'), 'next-step', false)
|
||||
agent.send(message('pending turn'), 'next-turn', false)
|
||||
if (config.cancelFirst) {
|
||||
controller.abort()
|
||||
await agent.whenIdle()
|
||||
}
|
||||
console.log('agent-shutdown: ready')
|
||||
}
|
||||
@@ -185,7 +185,7 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['run_code'])
|
||||
expect(assembly.sections.find(section => section.name === 'tools:sdk')?.text).toContain('session_trace')
|
||||
expect(names(native.agent)).toContain('session_search')
|
||||
expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory'])
|
||||
expect(names(minimal.agent)).toContain('session_search')
|
||||
await turn(coded.agent, 'Read this session lineage through run_code.', [
|
||||
toolCallResponse('ptc-search', 'run_code', {
|
||||
code: 'console.log(await tools.session_trace({}));',
|
||||
@@ -210,7 +210,7 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option
|
||||
const foreign = await agent('standard', otherWorkspace)
|
||||
await turn(foreign.agent, 'Keep OPTIONAL_SEARCH_MARKER in the other workspace.', [textResponse('Other marker recorded.')])
|
||||
await select(bundles.search, true)
|
||||
expect(names(live.agent)).not.toContain('session_search')
|
||||
expect(names(live.agent)).toContain('session_search')
|
||||
const searcher = await agent()
|
||||
await turn(searcher.agent, 'Find the previous marker in session history.', [
|
||||
toolCallResponse('search-history', 'session_search', { query: 'OPTIONAL_SEARCH_MARKER' }), textResponse('SEARCH_DONE'),
|
||||
@@ -220,11 +220,8 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option
|
||||
expect(toolResult(searcher.agent, 'search-history')).not.toContain(foreign.agent.session.id)
|
||||
expect(toolResult(searcher.agent, 'search-history')).toContain('OPTIONAL_SEARCH_MARKER')
|
||||
await select(bundles.search, false)
|
||||
expect(names(searcher.agent)).not.toContain('session_search')
|
||||
expect(names((await agent()).agent)).not.toContain('session_search')
|
||||
await turn(searcher.agent, 'Search the marker again using the retained capability.', [
|
||||
toolCallResponse('search-retained', 'session_search', { query: 'OPTIONAL_SEARCH_MARKER' }), textResponse('RETAINED_SEARCH_DONE'),
|
||||
])
|
||||
expect(toolResult(searcher.agent, 'search-retained')).toContain(seedId)
|
||||
const sessionId = searcher.agent.session.id
|
||||
await searcher.dispose()
|
||||
handles.delete(searcher)
|
||||
@@ -323,10 +320,10 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option
|
||||
expect(titleRequests(restored.agent)[0]?.data.titleProvider).toBe('session-title-first-prompt-llm')
|
||||
})
|
||||
|
||||
it('composes all five bundles while leaving minimal and host tools unchanged', async () => {
|
||||
it('composes all five bundles as global tools across every preset', async () => {
|
||||
for (const name of Object.values(bundles)) await select(name, true)
|
||||
let coded: Agent | undefined
|
||||
for (const preset of ['standard', 'cordis', 'ptc']) {
|
||||
for (const preset of ['standard', 'cordis', 'ptc', 'minimal']) {
|
||||
const owner = await agent(preset)
|
||||
expect(names(owner.agent)).toEqual(expect.arrayContaining(['session_search', 'terminal_open', 'ralph']))
|
||||
if (preset === 'ptc') coded = owner.agent
|
||||
@@ -340,9 +337,7 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option
|
||||
])
|
||||
expect(toolResult(coded, 'combined-program')).toContain(coded.session.id)
|
||||
expect(toolResult(coded, 'combined-program')).toContain('dsh-badge')
|
||||
const minimal = await agent('minimal')
|
||||
expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'working_directory'])
|
||||
expect(names()).toEqual(['working_directory'])
|
||||
expect(names()).toEqual(expect.arrayContaining(['session_search', 'terminal_open', 'ralph', 'working_directory']))
|
||||
for (const name of [...Object.values(bundles)].reverse()) await select(name, false)
|
||||
expect(names((await agent()).agent)).not.toContain('session_search')
|
||||
})
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
config:
|
||||
maxMembers: 8
|
||||
maxTasks: 256
|
||||
maxPendingMessagesPerMember: 64
|
||||
maxMessageBytes: 65536
|
||||
disposalTimeoutMs: 5000
|
||||
|
||||
|
||||
@@ -804,14 +804,14 @@ describe('headless stream-json snapshots', () => {
|
||||
if (typeof message !== 'object' || message === null || Array.isArray(message)) return false
|
||||
const source = (message as JsonObject).source
|
||||
return typeof source === 'object' && source !== null && !Array.isArray(source)
|
||||
&& (source as JsonObject).kind === 'team-message'
|
||||
&& (source as JsonObject).kind === 'agent-message'
|
||||
})
|
||||
})
|
||||
const steeredMessageIndex = implementerRows.findIndex((row) => {
|
||||
if (row.type !== 'user/message') return false
|
||||
const source = (row.data as JsonObject).source
|
||||
return typeof source === 'object' && source !== null && !Array.isArray(source)
|
||||
&& (source as JsonObject).kind === 'team-message'
|
||||
&& (source as JsonObject).kind === 'agent-message'
|
||||
})
|
||||
const openTurnStart = implementerRows.findLastIndex((row, index) => (
|
||||
index < steeredMessageIndex && row.type === 'turn/start'
|
||||
@@ -874,7 +874,7 @@ describe('headless stream-json snapshots', () => {
|
||||
"researcher",
|
||||
],
|
||||
"checkedRoster": true,
|
||||
"deliveredMessages": 2,
|
||||
"deliveredMessages": 0,
|
||||
"identityReminders": [
|
||||
"<system-reminder>
|
||||
You are teammate "implementer".
|
||||
@@ -892,7 +892,7 @@ describe('headless stream-json snapshots', () => {
|
||||
</system-reminder>",
|
||||
],
|
||||
"memberEdges": 4,
|
||||
"queuedMessages": 2,
|
||||
"queuedMessages": 0,
|
||||
"sessions": 4,
|
||||
"steerEvidence": {
|
||||
"completedAfterMessage": true,
|
||||
|
||||
@@ -158,8 +158,8 @@ it.skipIf(!built)('installs the exported registry\'s local closure and switches
|
||||
expect(step.install, JSON.stringify(step.install)).toMatchObject({ application: 'applied', enabled: true, version })
|
||||
expect(step.savedVersion).toBe(version)
|
||||
expect(step.installed).toMatchObject({ official: true, availability: 'profile', enabled: true, installed: true, version })
|
||||
expect(step.installed.rows.filter(row => row.preset !== undefined).map(row => row.preset).sort())
|
||||
.toEqual(['preset-cordis', 'preset-ptc', 'preset-standard'])
|
||||
const product = step.name.replace('@deepseek-ai/dsh-subagent-', '')
|
||||
expect(step.installed.rows.map(row => row.rowId)).toEqual([`subagent-${product}`, `tool-subagent-${product}`])
|
||||
expect(step.providerEnabled).toBe(true)
|
||||
expect(step.nativeArtifact).toBe(true)
|
||||
// A transitive repository package the closure needs can only have come from this run's artifacts.
|
||||
|
||||
@@ -13,15 +13,12 @@ import {
|
||||
loadProfile,
|
||||
PluginPackages,
|
||||
type Profile,
|
||||
type ProfilePatch,
|
||||
} from '@deepseek-ai/dsh-app-boot'
|
||||
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
|
||||
import { SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { dump, load } from 'js-yaml'
|
||||
import { entryListSchema } from '@deepseek-ai/cordis-plugin-include'
|
||||
import { bundlePatchPaths, composeEntries } from '@deepseek-ai/dsh-app-boot'
|
||||
import { createPluginProfile } from '../../desktop/src/project-manager.ts'
|
||||
/** Profile entry ids whose volatile fields these scenarios edit through Settings. */
|
||||
@@ -63,7 +60,7 @@ const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
|
||||
*/
|
||||
async function bootWeb(
|
||||
profileHome: string,
|
||||
extra: ProfilePatch[] = [],
|
||||
extra: PatchOptions[] = [],
|
||||
profilePackages: readonly string[] = [],
|
||||
profileBundles?: readonly string[],
|
||||
surface: 'web' | 'desktop' | 'spec' = 'spec',
|
||||
@@ -532,165 +529,74 @@ describe('the shipped Web composition', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('product Bundle and user-preset intersection', () => {
|
||||
const presetIds = ['products-none', 'products-codex', 'products-claude', 'products-both'] as const
|
||||
type Product = 'codex' | 'claude-code'
|
||||
type PresetId = typeof presetIds[number]
|
||||
describe.each(['web', 'desktop'] as const)('%s native subagent bundles', (surface) => {
|
||||
const products = ['codex', 'claude-code'] as const
|
||||
type Product = typeof products[number]
|
||||
const packageDir = (product: Product): string => (product === 'codex' ? CODEX_PACKAGE_DIR : CLAUDE_CODE_PACKAGE_DIR)
|
||||
const toolName = (product: Product): string => `subagent_${product.replaceAll('-', '_')}`
|
||||
|
||||
async function bootProducts(installed: readonly Product[]): Promise<Context> {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-product-presets-'))
|
||||
const definitions: import('@deepseek-ai/cordis-plugin-loader').EntryOptions[] = []
|
||||
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 })
|
||||
for (const id of presetIds) {
|
||||
const plugins = load(standard, { schema: entryListSchema }) as import('@deepseek-ai/cordis-plugin-loader').EntryOptions[]
|
||||
// User presets declare their own delegation rows; provider bundles target only the three full shipped presets.
|
||||
if (id === 'products-codex' || id === 'products-both') {
|
||||
plugins.push({ id: 'custom-tool-subagent-codex', name: '@deepseek-ai/dsh-tool-subagent', config: {
|
||||
provider: 'codex', toolName: 'subagent_codex', maxDepth: 'provider-managed',
|
||||
} })
|
||||
}
|
||||
if (id === 'products-claude' || id === 'products-both') {
|
||||
plugins.push({ id: 'custom-tool-subagent-claude-code', name: '@deepseek-ai/dsh-tool-subagent', config: {
|
||||
provider: 'claude-code', toolName: 'subagent_claude_code', maxDepth: 'provider-managed',
|
||||
} })
|
||||
}
|
||||
definitions.push({ id: `preset-${id}`, name: '@deepseek-ai/dsh-agent-preset', config: { id, plugins } })
|
||||
}
|
||||
const packageDir = (product: Product): string => (
|
||||
product === 'codex' ? CODEX_PACKAGE_DIR : CLAUDE_CODE_PACKAGE_DIR
|
||||
)
|
||||
const packageName = (product: Product): string => (
|
||||
product === 'codex'
|
||||
? '@deepseek-ai/dsh-subagent-codex'
|
||||
: '@deepseek-ai/dsh-subagent-claude-code'
|
||||
)
|
||||
return await bootWeb(root, [{ insert: definitions }], installed.map(packageDir), [
|
||||
'@deepseek-ai/dsh-base',
|
||||
'@deepseek-ai/dsh-web-app',
|
||||
...installed.map(packageName),
|
||||
])
|
||||
async function bootSelected(installed: readonly Product[], extra: PatchOptions[] = []): Promise<Context> {
|
||||
const root = await mkdtemp(join(tmpdir(), `dsh-${surface}-native-bundles-`))
|
||||
onTestFinished(async () => { await rm(root, { recursive: true, force: true }) })
|
||||
return await bootWeb(root, extra, installed.map(packageDir), [
|
||||
'@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', ...installed.map(product => `@deepseek-ai/dsh-subagent-${product}`),
|
||||
], surface)
|
||||
}
|
||||
|
||||
it('composes the intersection of installed Bundles and enabled preset rows', async () => {
|
||||
const enabledByPreset: Record<PresetId, Product[]> = {
|
||||
'products-none': [],
|
||||
'products-codex': ['codex'],
|
||||
'products-claude': ['claude-code'],
|
||||
'products-both': ['codex', 'claude-code'],
|
||||
}
|
||||
const scenarios: Array<{ installed: Product[]; presets: readonly PresetId[] }> = [
|
||||
{ installed: [], presets: ['products-both'] },
|
||||
{ installed: ['codex'], presets: ['products-both'] },
|
||||
{ installed: ['claude-code'], presets: ['products-both'] },
|
||||
{ installed: ['codex', 'claude-code'], presets: presetIds },
|
||||
]
|
||||
async function presetTools(productCtx: Context, preset: string): Promise<string[]> {
|
||||
const handle = await productCtx.agents.create({
|
||||
sessionId: SessionId(`native-${surface}-${preset}-${randomUUID()}`),
|
||||
setup: owner => productCtx.agentPresets.mount(owner, preset).then(() => undefined),
|
||||
})
|
||||
try {
|
||||
return toolNames(productCtx, handle.agent).filter(name => /(?:subagent|custom)_(?:codex|claude_code)$/.test(name))
|
||||
} finally { await handle.dispose() }
|
||||
}
|
||||
|
||||
for (const { installed, presets } of scenarios) {
|
||||
const productCtx = await bootProducts(installed)
|
||||
it.each([[[]], [['codex']], [['claude-code']], [['codex', 'claude-code']]] as Array<[Product[]]>)(
|
||||
'adds the selected %j delegation tools to every preset as global tools', async (installed) => {
|
||||
const productCtx = await bootSelected(installed)
|
||||
const spawn = vi.spyOn(productCtx.subprocess, 'spawn')
|
||||
try {
|
||||
expect(productCtx.subagents.list()
|
||||
.filter(name => name === 'codex' || name === 'claude-code')
|
||||
.sort())
|
||||
expect(productCtx.subagents.list().filter(name => (products as readonly string[]).includes(name)).sort())
|
||||
.toEqual([...installed].sort())
|
||||
for (const id of presets) {
|
||||
const handle = await productCtx.agents.create({
|
||||
sessionId: SessionId(`preset-${id}-${installed.join('-') || 'none'}-${randomUUID()}`),
|
||||
setup: agentCtx => productCtx.agentPresets.mount(agentCtx, id).then(() => undefined),
|
||||
})
|
||||
try {
|
||||
const productTools = enabledByPreset[id]
|
||||
.filter(product => installed.includes(product))
|
||||
.map(product => product === 'codex' ? 'subagent_codex' : 'subagent_claude_code')
|
||||
.sort()
|
||||
const tools = toolNames(productCtx, handle.agent)
|
||||
expect(tools.filter(name => name === 'subagent_codex' || name === 'subagent_claude_code'))
|
||||
.toEqual(productTools)
|
||||
expect(tools).toEqual(expect.arrayContaining(['job_kill', 'job_list', 'job_output']))
|
||||
for (const productTool of productTools) {
|
||||
expect(toolParameterNames(productCtx, handle.agent, productTool)).toEqual([
|
||||
'cwd', 'description', 'prompt',
|
||||
])
|
||||
}
|
||||
} finally {
|
||||
await handle.dispose()
|
||||
}
|
||||
const expected = installed.map(toolName).sort()
|
||||
for (const preset of ['standard', 'cordis', 'ptc', 'minimal']) {
|
||||
expect(await presetTools(productCtx, preset)).toEqual(expected)
|
||||
}
|
||||
const handle = await productCtx.agents.create({
|
||||
sessionId: SessionId(`native-${surface}-params-${randomUUID()}`),
|
||||
setup: owner => productCtx.agentPresets.mount(owner, 'standard').then(() => undefined),
|
||||
})
|
||||
try {
|
||||
for (const product of installed) {
|
||||
expect(toolParameterNames(productCtx, handle.agent, toolName(product))).toEqual(['cwd', 'description', 'prompt'])
|
||||
}
|
||||
} finally { await handle.dispose() }
|
||||
expect(spawn).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
spawn.mockRestore()
|
||||
await productCtx.fiber.dispose()
|
||||
}
|
||||
}
|
||||
}, 120_000)
|
||||
})
|
||||
}, 120_000)
|
||||
|
||||
describe.each(['web', 'desktop'] as const)('%s native bundle adoption', (surface) => {
|
||||
const products = ['codex', 'claude-code'] as const
|
||||
const bundleNames = products.map(product => `@deepseek-ai/dsh-subagent-${product}`)
|
||||
|
||||
async function bootSelected(extra: ProfilePatch[]): Promise<Context> {
|
||||
const root = await mkdtemp(join(tmpdir(), `dsh-${surface}-native-adoption-`))
|
||||
onTestFinished(async () => { await rm(root, { recursive: true, force: true }) })
|
||||
return await bootWeb(root, extra, [CODEX_PACKAGE_DIR, CLAUDE_CODE_PACKAGE_DIR], [
|
||||
'@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app', ...bundleNames,
|
||||
], surface)
|
||||
}
|
||||
|
||||
it('keeps selected provider identities and later tool overrides without duplicating custom preset tools', async () => {
|
||||
const definition = composeEntries([webPatches('adoption')]).find(row => row.id === 'preset-cordis')!
|
||||
.config as import('@deepseek-ai/dsh-agent-preset-registry').PresetDefinition
|
||||
const config = { ...definition, plugins: [...definition.plugins, ...products.map(product => ({
|
||||
id: `tool-subagent-${product}`, name: '@deepseek-ai/dsh-tool-subagent', disabled: product === 'codex',
|
||||
config: { provider: product, toolName: `custom_${product.replaceAll('-', '_')}`, maxDepth: 'provider-managed' },
|
||||
}))] }
|
||||
it('keeps provider identities and applies later Host row overrides', async () => {
|
||||
const providerConfig = { model: 'adoption-model', env: { DSH_NATIVE_ADOPTION: 'preserved' } }
|
||||
const productCtx = await bootSelected([
|
||||
const productCtx = await bootSelected(products, [
|
||||
...products.map(product => ({ id: `subagent-${product}`, config: providerConfig })),
|
||||
{ preset: 'preset-standard', id: 'tool-subagent-codex', disabled: true },
|
||||
{ preset: 'preset-standard', id: 'tool-subagent-claude-code', config: {
|
||||
{ id: 'tool-subagent-codex', disabled: true },
|
||||
{ id: 'tool-subagent-claude-code', config: {
|
||||
provider: 'claude-code', toolName: 'custom_claude_code', maxDepth: 'provider-managed',
|
||||
} },
|
||||
{ id: 'preset-cordis', config },
|
||||
])
|
||||
const spawn = vi.spyOn(productCtx.subprocess, 'spawn')
|
||||
try {
|
||||
expect(productCtx.subagents.list()).toEqual(expect.arrayContaining([...products]))
|
||||
for (const product of products) {
|
||||
expect([...productCtx.loader.entries()].find(entry => entry.options.id === `subagent-${product}`)?.options.config)
|
||||
.toMatchObject(providerConfig)
|
||||
}
|
||||
for (const [preset, expected] of [
|
||||
['standard', ['custom_claude_code']], ['cordis', ['custom_claude_code']],
|
||||
['ptc', ['subagent_claude_code', 'subagent_codex']], ['minimal', []],
|
||||
] as const) {
|
||||
const handle = await productCtx.agents.create({
|
||||
sessionId: SessionId(`adoption-${surface}-${preset}-${randomUUID()}`),
|
||||
setup: owner => productCtx.agentPresets.mount(owner, preset).then(() => undefined),
|
||||
})
|
||||
try {
|
||||
expect(toolNames(productCtx, handle.agent).filter(name => /(?:subagent|custom)_(?:codex|claude_code)/.test(name)))
|
||||
.toEqual(expected)
|
||||
} finally { await handle.dispose() }
|
||||
for (const preset of ['standard', 'ptc', 'minimal']) {
|
||||
expect(await presetTools(productCtx, preset)).toEqual(['custom_claude_code'])
|
||||
}
|
||||
expect(spawn).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
spawn.mockRestore()
|
||||
await productCtx.fiber.dispose()
|
||||
}
|
||||
}, 120_000)
|
||||
|
||||
it('reports a redundant manual tool instead of silently replacing the bundle contribution', async () => {
|
||||
const productCtx = await bootSelected([{ preset: 'preset-standard', insert: [{
|
||||
id: 'manual-codex', name: '@deepseek-ai/dsh-tool-subagent', config: {
|
||||
provider: 'codex', toolName: 'subagent_codex', maxDepth: 'provider-managed',
|
||||
},
|
||||
}] }])
|
||||
try {
|
||||
expect((await productCtx.agentPresets.list()).find(preset => preset.id === 'standard')?.broken)
|
||||
.toContain('already registered')
|
||||
expect(productCtx.profileContext.startedBundles).toEqual(expect.arrayContaining(bundleNames))
|
||||
} finally { await productCtx.fiber.dispose() }
|
||||
}, 120_000)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/** Keyless SIGTERM regression for Agent cancellation during Web teardown. */
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { createInterface } from 'node:readline'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { execa } from 'execa'
|
||||
import { expect, it } from 'vitest'
|
||||
import { resolveExampleLaunch } from '@deepseek-ai/dsh-loader-smoke'
|
||||
|
||||
it.each([
|
||||
{ state: 'idle', cancelFirst: false },
|
||||
{ state: 'running', cancelFirst: false },
|
||||
{ state: 'idle', cancelFirst: true },
|
||||
{ state: 'running', cancelFirst: true },
|
||||
])('preserves Agent cleanup during Web shutdown ($state, cancel first: $cancelFirst)', { retry: 0 }, async ({ state, cancelFirst }) => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-web-agent-shutdown-'))
|
||||
try {
|
||||
const patch = join(root, 'shutdown.patch.yml')
|
||||
const resultPath = join(root, 'result.json')
|
||||
const interrupt = join(root, 'interrupt')
|
||||
await writeFile(patch, [
|
||||
'- id: hmr',
|
||||
' disabled: true',
|
||||
'- id: session-title-llm',
|
||||
' disabled: true',
|
||||
'- insert:',
|
||||
` - name: ${JSON.stringify(new URL('./fixtures/agent-shutdown.mjs', import.meta.url).href)}`,
|
||||
` config: ${JSON.stringify({ root, result: resultPath, interrupt, running: state === 'running', cancelFirst })}`,
|
||||
'',
|
||||
].join('\n'))
|
||||
const launch = resolveExampleLaunch({
|
||||
srcBin: fileURLToPath(new URL('../src/bin.ts', import.meta.url)),
|
||||
tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)),
|
||||
configArgs: ['--profile', 'web', '--patch', patch, '--no-open', '--port', '0'],
|
||||
env: {
|
||||
DSH_HOME: join(root, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(root, '.agents'),
|
||||
DSH_TELEMETRY_DISABLED: '1',
|
||||
},
|
||||
})
|
||||
const child = execa(launch.command, launch.args, {
|
||||
cwd: root, env: launch.env, stdin: 'pipe',
|
||||
timeout: 90_000, killSignal: 'SIGKILL', reject: false,
|
||||
})
|
||||
const lines = createInterface({ input: child.stdout })
|
||||
try {
|
||||
const ready = Promise.withResolvers<undefined>()
|
||||
lines.on('line', (line) => {
|
||||
if (line === 'agent-shutdown: ready') ready.resolve(undefined)
|
||||
})
|
||||
await Promise.race([
|
||||
ready.promise,
|
||||
child.then((result) => { throw new Error(`Web exited before readiness: ${result.stderr}`) }),
|
||||
])
|
||||
if (process.platform === 'win32') await writeFile(interrupt, '')
|
||||
else child.kill('SIGTERM')
|
||||
const result = await child
|
||||
expect(result.timedOut, result.stderr).toBe(false)
|
||||
expect(result.signal, result.stderr).toBeUndefined()
|
||||
expect(result.exitCode, result.stderr).toBe(0)
|
||||
expect(result.stderr).not.toMatch(/dsh: fatal|projection registration is not active/u)
|
||||
const evidence: unknown = JSON.parse(await readFile(resultPath, 'utf8'))
|
||||
expect(evidence).toMatchObject({
|
||||
abortHandled: 1,
|
||||
status: 'idle',
|
||||
nextStep: [],
|
||||
nextTurn: [],
|
||||
cancelled: [
|
||||
{ data: { target: 'next-step', removedCount: 1 } },
|
||||
{ data: { target: 'next-turn', removedCount: 1 } },
|
||||
],
|
||||
})
|
||||
if (state === 'running') {
|
||||
expect(evidence).toMatchObject({
|
||||
lastEvent: { type: 'turn/end', data: { reason: { kind: 'aborted', reason: { kind: cancelFirst ? 'parent' : 'disposed' } } } },
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
lines.close()
|
||||
child.kill('SIGKILL')
|
||||
await child
|
||||
}
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -57,8 +57,8 @@
|
||||
en: f48baa680e841b0d
|
||||
zh: 25ded1aeaac6dad1
|
||||
/deepseek-harness-desktop/package/unsigned-windows-test-installer:
|
||||
en: cfd5780dd26a8e46
|
||||
zh: f7a16b1ede793b7b
|
||||
en: ed94776d1c3b7517
|
||||
zh: 9e6b1738b409d5e7
|
||||
/deepseek-harness-desktop/package/windows-installer-interface:
|
||||
en: 1fc1db8f54475c8f
|
||||
zh: dd834ed206070396
|
||||
@@ -66,11 +66,14 @@
|
||||
en: be1379e4b4703e14
|
||||
zh: 244ccd2874bb155a
|
||||
/deepseek-harness-desktop/updates:
|
||||
en: 10d58aa5bbffbd67
|
||||
zh: d9c2476e744a6537
|
||||
en: 41d0f50eb7173342
|
||||
zh: ce940ef3cbcc6fa5
|
||||
/deepseek-harness-desktop/updates/local-desktop-settings:
|
||||
en: 2e57457c44c85b5f
|
||||
zh: 5b8510a3dc4c6c9d
|
||||
/deepseek-harness-desktop/updates/mandatory-update-policy:
|
||||
en: 1527dceec540649e
|
||||
zh: 543782c36f91f893
|
||||
en: 54596bef0706d305
|
||||
zh: 48737302dcf3c939
|
||||
/deepseek-harness-desktop/updates/local-updater-qualification:
|
||||
en: bc50fd8a4d96c227
|
||||
zh: fb0a6ce80c31cda6
|
||||
|
||||
+25
-1
@@ -354,6 +354,8 @@ pnpm run package:desktop:win:x64:unsigned
|
||||
|
||||
The command requires `DSH_DESKTOP_APP_ID` and the normal build dependencies, including Python and Visual C++ build tools for native modules. Set `PYTHON` to the Python executable when it is absent from `PATH`. It writes the installer to `.desktop-build/targets/win-x64/unsigned-artifacts/`, omits automatic-update configuration, strips signing credentials, and creates no release completion record. It does not require EV credentials or an update origin. The signed packaging and upload commands retain their release requirements.
|
||||
|
||||
<a id="windows-uninstall-rules"></a>
|
||||
|
||||
### Windows installer interface
|
||||
|
||||
The Windows installer uses native NSIS pages with light and dark palettes, system shadows, an editable installation directory, and a finish page whose launch checkbox is selected by default. Installation is restricted to the current user. Clicking Install or pressing Enter validates the current path; drive roots remain invalid after normalization, including repeated trailing separators. New destinations must be empty, and nonempty destinations must be registered installations. Running executables at the affected installation path produce a native prompt and remain running; same-named applications in other directories do not block installation. Silent updates wait up to ten seconds for the affected application to exit, then stop with exit code 2 if it is still running.
|
||||
@@ -445,6 +447,26 @@ If task teardown fails after confirmed Host exit, installation is refused and th
|
||||
|
||||
Confirmed Host exit without successful task teardown displays localized recovery guidance in both ordinary and mandatory update dialogs. A typed preparation cause selects that guidance in each locale; changing translated wording cannot reclassify the failure. “View technical details” is collapsed by default and exposes only exit status, signal, shutdown acknowledgement, and deadline facts, not plugin stderr. Expanding it neither retries nor authorizes installation.
|
||||
|
||||
<a id="local-desktop-settings"></a>
|
||||
|
||||
### Local desktop settings
|
||||
|
||||
The [shell configuration decision](../../.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md) defines Host-independent ownership and the criteria for evaluating existing settings for migration.
|
||||
|
||||
The Electron main process reads `app.getPath('userData')/desktop/settings.json` once at startup, independently of Host and Cordis configuration. The default packaged paths are `%APPDATA%\@deepseek-ai\dsh-desktop\desktop\settings.json` on Windows and `~/Library/Application Support/@deepseek-ai/dsh-desktop/desktop/settings.json` on macOS. Development uses the `userData` path printed by its launcher. `DSH_HOME` does not relocate this file.
|
||||
|
||||
On first launch, including after an upgrade, Desktop creates this document if it is absent:
|
||||
|
||||
```json
|
||||
{
|
||||
"updates": {
|
||||
"allowTestAuthPopupWindow": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Existing documents are never overwritten; an omitted `updates` object or field resolves to `false`. Set the field to `true` to permit test authentication dialogs, then fully quit and restart the application. Set it back to `false` and restart to suppress them again. Creation, read, or validation failures use the default `false` without blocking startup. Desktop attempts to warn through the Electron console with the file path; unavailable logging does not block startup. Correct the file and restart to apply an explicit setting. Unknown fields are preserved. Installation updates retain this user-data file; Windows uninstallation removes it under the [uninstall rules](#windows-uninstall-rules).
|
||||
|
||||
<a id="mandatory-update-policy"></a>
|
||||
|
||||
### Mandatory update policy
|
||||
@@ -466,7 +488,9 @@ Packaging reads `.env.windows` or `.env.macos`: `DSH_DESKTOP_AUTO_UPDATE_ENV=tes
|
||||
|
||||
Durations are integers from 1000 through 2147483647 milliseconds. Startup and scheduled polling are independent of business requests; foreground/resume checks respect the next due time, while manual checks bypass it and join any request in flight. The client sends the installed platform, architecture, DSH_CLIENT_VERSION, bundled dsh version, current locale and UTC offset, an empty bundle ID, and fixed Nightly. It uses no business login credentials or installation ID.
|
||||
|
||||
With `feishu-test`, an HTTP 401 JSON response containing `error.code: "UNAUTHENTICATED"` offers login during user-initiated checks and the packaged application's initial startup check, without waiting for the local backend. A localized explanation identifies the test build, the need for Feishu authentication, and that login neither downloads nor installs updates. Confirmation closes the explanation before opening a sandboxed window at the configured origin’s root, not a response-provided login URL. Concurrent checks reuse the entire confirmation/login operation and focus its existing window. Press F12 in the test login window to open detached DevTools for diagnosis. Cancellation does not trigger repeated prompts from periodic or foreground checks; users can retry manually.
|
||||
With `feishu-test`, an HTTP 401 JSON response containing `error.code: "UNAUTHENTICATED"` offers login only when the local `updates.allowTestAuthPopupWindow` setting is `true`. It defaults to `false`, suppressing the entire authentication dialog flow during startup, manual update checks, mandatory-update refreshes, and deferred or repeated failures. Test gateway cookies are process-local. With popups disabled at startup, a gateway requiring authentication cannot provide new mandatory-update decisions during that process; enable the setting, restart and sign in to obtain them. Policy requests still enforce gateway authentication, and an authentication failure never clears a known mandatory block. Ordinary updates and product account authentication remain independent. Production keeps its anonymous policy requests regardless of this setting.
|
||||
|
||||
When permitted, login is offered during user-initiated checks and the packaged application's initial startup check, without waiting for the local backend. A localized explanation identifies the test build, the need for Feishu authentication, and that login neither downloads nor installs updates. Confirmation closes the explanation before opening a sandboxed window at the configured origin’s root, not a response-provided login URL. Concurrent checks reuse the entire confirmation/login operation and focus its existing window. Press F12 in the test login window to open detached DevTools for diagnosis. Cancellation does not trigger repeated prompts from periodic or foreground checks; users can retry manually.
|
||||
|
||||
Login and policy requests share an in-memory Session, separate from product windows and the updater; restarting requires a new login. Closing cancels login, and navigation failure provides localized retry guidance. Returning to the service triggers a fresh policy query; a redirect, cookie, or HTTP 422 is not a valid policy decision. Cancellation, expiry, and invalid responses retain any known mandatory block. Fixed login outcomes appear in process diagnostics and the optional update journal; cookies, OAuth parameters, and remote error text are not recorded by the login controller. Live Harness gateway/API integration and macOS login qualification remain unverified.
|
||||
|
||||
|
||||
@@ -356,6 +356,8 @@ pnpm run package:desktop:win:x64:unsigned
|
||||
|
||||
该命令要求设置 `DSH_DESKTOP_APP_ID` 并具备常规构建依赖,包括编译原生模块所需的 Python 和 Visual C++ 构建工具。Python 不在 `PATH` 中时,将 `PYTHON` 设置为其可执行文件路径。命令将安装包写入 `.desktop-build/targets/win-x64/unsigned-artifacts/`,省略自动更新配置,清除签名凭据,且不生成发布完成记录。它不需要 EV 凭据或更新源地址。签名打包和上传命令仍遵循正式发布要求。
|
||||
|
||||
<a id="windows-uninstall-rules"></a>
|
||||
|
||||
### Windows 安装界面
|
||||
|
||||
Windows 安装程序使用原生 NSIS 页面,提供亮暗配色、系统阴影、可编辑的安装目录,以及默认勾选立即启动的完成页。安装仅面向当前用户。点击安装或按 Enter 均校验当前路径;路径规范化后仍拒绝磁盘根目录,包括带重复末尾分隔符的写法。新安装位置必须为空,非空位置必须是已登记的安装目录。受影响安装路径中的程序运行时显示系统提示,并保持应用运行;其他目录中的同名应用不阻止安装。静默更新最多等待受影响应用退出十秒,若仍在运行则以退出码 2 结束。
|
||||
@@ -447,6 +449,26 @@ Windows 下载完成后的更新确认说明应用会在安装期间关闭、完
|
||||
|
||||
已确认 Host 退出但任务未成功收尾时,常规与强更弹窗均展示本地化恢复提示。两种语言都根据类型化的准备失败原因选择提示,翻译文案变化不会改变失败分类。“查看技术详情”默认折叠,仅展示退出状态、信号、关闭确认和截止时间事实,不展示插件 stderr。展开详情既不重试,也不授权安装。
|
||||
|
||||
<a id="local-desktop-settings"></a>
|
||||
|
||||
### 本地桌面设置
|
||||
|
||||
[壳层配置决策](../../.agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.zh.md)说明独立于 Host 的配置归属,以及评估现有设置迁移的标准。
|
||||
|
||||
Electron 主进程在启动时读取一次 `app.getPath('userData')/desktop/settings.json`,独立于 Host 和 Cordis 配置。打包应用默认路径为 Windows 的 `%APPDATA%\@deepseek-ai\dsh-desktop\desktop\settings.json` 和 macOS 的 `~/Library/Application Support/@deepseek-ai/dsh-desktop/desktop/settings.json`。开发模式使用启动器打印的 `userData` 路径。`DSH_HOME` 不改变此文件的位置。
|
||||
|
||||
首次启动(包括升级后首次启动)时,Desktop 会在文件缺失时创建以下内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"updates": {
|
||||
"allowTestAuthPopupWindow": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
已有文件不会被覆盖;缺少 `updates` 对象或该字段时均按 `false` 处理。将字段设为 `true` 后完全退出并重启应用,即可允许测试鉴权弹窗。改回 `false` 并重启即可再次禁止。创建、读取或校验失败时使用默认值 `false`,不阻止启动。Desktop 尝试在 Electron 控制台记录包含文件路径的警告;日志不可用也不阻止启动。修正文件并重启后才会应用显式设置。未知字段会保留。安装更新保留此用户数据文件;Windows 卸载按[卸载规则](#windows-uninstall-rules)删除它。
|
||||
|
||||
<a id="mandatory-update-policy"></a>
|
||||
|
||||
### 强制更新策略
|
||||
@@ -468,7 +490,9 @@ Windows 下载完成后的更新确认说明应用会在安装期间关闭、完
|
||||
|
||||
时长必须是 1000 至 2147483647 毫秒的整数。启动与定时轮询独立于业务请求;前台/恢复检查遵守下次到期时间,手动检查绕过该时间并复用在途请求。客户端发送已安装平台、架构、DSH_CLIENT_VERSION、内置 dsh 版本、当前语言与 UTC 偏移、空 bundle ID 和固定 Nightly。不使用业务登录凭据或安装 ID。
|
||||
|
||||
启用 `feishu-test` 时,包含 `error.code: "UNAUTHENTICATED"` 的 HTTP 401 JSON 响应会在用户主动检查和打包应用首次启动检查时提供登录入口,不等待本地后端就绪。本地化说明指出这是测试版、需要飞书鉴权,且登录不会下载或安装更新。确认后先关闭说明,再打开配置源站根路径的沙箱窗口,不使用响应中的登录 URL。并发检查复用整个确认/登录流程,并聚焦已有窗口。在测试环境登录窗口按 F12 可打开独立的 DevTools 进行排查。取消后,定时或前台检查不会反复弹窗;用户可手动重试。
|
||||
启用 `feishu-test` 时,包含 `error.code: "UNAUTHENTICATED"` 的 HTTP 401 JSON 响应仅在本地 `updates.allowTestAuthPopupWindow` 为 `true` 时提供登录入口。该字段默认为 `false`,禁止启动、手动检查更新、强更刷新以及延迟或重复失败触发的整个鉴权弹窗流程。测试网关 Cookie 仅在当前进程有效。启动时关闭弹窗后,需要鉴权的网关无法在本次进程中提供新的强更决策;需开启设置、重启并登录后才能获取。策略请求仍需网关鉴权,鉴权失败不会解除已知强更阻塞。常规更新和产品账号鉴权保持独立。正式环境始终使用原有匿名策略请求,不受此设置影响。
|
||||
|
||||
允许弹窗时,用户主动检查和打包应用首次启动检查会提供登录入口,不等待本地后端就绪。本地化说明指出这是测试版、需要飞书鉴权,且登录不会下载或安装更新。确认后先关闭说明,再打开配置源站根路径的沙箱窗口,不使用响应中的登录 URL。并发检查复用整个确认/登录流程,并聚焦已有窗口。在测试环境登录窗口按 F12 可打开独立的 DevTools 进行排查。取消后,定时或前台检查不会反复弹窗;用户可手动重试。
|
||||
|
||||
登录和策略请求共用内存 Session,与产品窗口及 updater 隔离;应用重启后需要重新登录。关闭窗口取消登录,导航失败提供本地化重试提示。返回服务后重新查询策略;重定向、Cookie 或 HTTP 422 都不是有效策略决定。取消、登录过期及无效响应均保留已知强更阻塞。固定登录结果写入进程诊断及可选更新日志;登录控制器不记录 Cookie、OAuth 参数或远程错误原文。真实 Harness 网关/API 联调及 macOS 登录验收仍未完成。
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ import { DesktopMandatoryUpdatePolicy, resolveDesktopPolicyConfig, type DesktopP
|
||||
import { desktopClientMetadata, desktopClientVersion } from './client-metadata.ts'
|
||||
import { DesktopMandatoryUpdateWindow } from './mandatory-update-window.ts'
|
||||
import { DesktopPolicyTestAuth } from './policy-test-auth.ts'
|
||||
import { readDesktopSettings } from './settings.ts'
|
||||
import { DesktopUpdateDialog, type UpdateDialogOptions } from './update-dialog.ts'
|
||||
import { readDesktopRuntime } from './runtime-tree.ts'
|
||||
import { DesktopBrowserGuests } from './browser-guests.ts'
|
||||
@@ -313,6 +314,7 @@ function createWindow(preload: string, show = false, primary = false): BrowserWi
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const desktopSettings = readDesktopSettings(app.getPath('userData'))
|
||||
void pruneCrashReports(app.getPath('logs'))
|
||||
const journalDirectory = process.env.DSH_DESKTOP_UPDATE_JOURNAL_DIR
|
||||
const updateJournal = journalDirectory === undefined ? undefined : new DesktopUpdateJournal(journalDirectory, app.getVersion())
|
||||
@@ -875,6 +877,7 @@ async function main(): Promise<void> {
|
||||
void authenticatePolicy().catch((error: unknown) => { console.error(error) })
|
||||
}
|
||||
const queuePolicyAuthentication = (): void => {
|
||||
if (!desktopSettings.updates.allowTestAuthPopupWindow) return
|
||||
if (authenticationOperation !== undefined) {
|
||||
policyAuth?.focus(); updateDialog.focus()
|
||||
return
|
||||
@@ -883,7 +886,8 @@ async function main(): Promise<void> {
|
||||
flushQueuedPolicyAuthentication()
|
||||
}
|
||||
const runPolicyAuthentication = async () => {
|
||||
if (policyAuth === undefined || mandatoryPolicy === undefined || quitting) return undefined
|
||||
if (!desktopSettings.updates.allowTestAuthPopupWindow
|
||||
|| policyAuth === undefined || mandatoryPolicy === undefined || quitting) return undefined
|
||||
const parent = mandatoryUI?.confirmationWindow ?? currentDialogWindow()
|
||||
if (parent === undefined) return undefined
|
||||
const consent = await updateDialog.show(parent, { type: 'info', title: locale.messages.policyLoginTitle,
|
||||
@@ -905,7 +909,7 @@ async function main(): Promise<void> {
|
||||
const checkPolicyManually = async (authentication: 'immediate' | 'deferred' = 'immediate') => {
|
||||
if (authenticationOperation !== undefined) return authenticatePolicy()
|
||||
const policy = await mandatoryPolicy?.check('manual', true)
|
||||
if (policy?.error !== 'authentication-required') return policy
|
||||
if (policy?.error !== 'authentication-required' || !desktopSettings.updates.allowTestAuthPopupWindow) return policy
|
||||
if (authentication === 'immediate') return authenticatePolicy()
|
||||
queuePolicyAuthentication()
|
||||
return policy
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Local Electron shell settings, loaded once per process independently of the Host.
|
||||
* Ownership: .agents/notes/implemented/architecture/2026-10-08-desktop-shell-configuration.md.
|
||||
*/
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
/** Resolved shell preferences; absent fields use the defaults without rewriting existing files. */
|
||||
export interface DesktopSettings {
|
||||
readonly updates: {
|
||||
readonly allowTestAuthPopupWindow: boolean
|
||||
}
|
||||
}
|
||||
|
||||
function record(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Read shell settings, creating the default document exclusively when missing.
|
||||
* @param userData - Electron's userData directory, independent of DSH_HOME.
|
||||
* @returns Validated settings, or defaults when reading, creating or validating fails; changes require a restart.
|
||||
*/
|
||||
export function readDesktopSettings(userData: string): DesktopSettings {
|
||||
const directory = join(userData, 'desktop')
|
||||
const path = join(directory, 'settings.json')
|
||||
const defaults: DesktopSettings = { updates: { allowTestAuthPopupWindow: false } }
|
||||
try {
|
||||
let raw: string
|
||||
try { raw = readFileSync(path, 'utf8') } catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
||||
mkdirSync(directory, { recursive: true, mode: 0o700 })
|
||||
try { writeFileSync(path, `${JSON.stringify(defaults, null, 2)}\n`, { flag: 'wx', mode: 0o600 }) } catch (error) {
|
||||
// An existing document belongs to the user, including one created during initialization.
|
||||
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
|
||||
}
|
||||
raw = readFileSync(path, 'utf8')
|
||||
}
|
||||
const value: unknown = JSON.parse(raw)
|
||||
if (!record(value) || (value.updates !== undefined && !record(value.updates))) {
|
||||
throw new Error('expected a JSON object with an optional updates object')
|
||||
}
|
||||
const allow = value.updates?.allowTestAuthPopupWindow
|
||||
if (allow !== undefined && typeof allow !== 'boolean') {
|
||||
throw new Error('updates.allowTestAuthPopupWindow must be true or false')
|
||||
}
|
||||
return { updates: { allowTestAuthPopupWindow: allow ?? false } }
|
||||
} catch (error) {
|
||||
try {
|
||||
console.warn(`desktop settings: ${path}: using defaults because settings could not be loaded`, error)
|
||||
} catch (_logError) {
|
||||
// Diagnostic output is best effort; its failure must not make optional settings fatal.
|
||||
// TODO: Persist settings diagnostics when console output is unavailable, without blocking startup.
|
||||
}
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"disabled": [
|
||||
"Checking for updates…",
|
||||
"You’re up to date!"
|
||||
],
|
||||
"returned": [
|
||||
"Checking for updates…",
|
||||
"You’re up to date!",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { WINDOWS_TITLEBAR_HEIGHT } from '../src/windows-layout.ts'
|
||||
import { afterEach, beforeEach, describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import type { IpcMainInvokeEvent } from 'electron'
|
||||
import { join } from 'node:path'
|
||||
import { mkdtempSync, readFileSync, readdirSync, rmSync } from 'node:fs'
|
||||
import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import type { MenuItemConstructorOptions, MessageBoxOptions } from 'electron'
|
||||
import { DESKTOP_IPC, type DesktopUpdateState } from '../src/ipc.ts'
|
||||
@@ -371,6 +371,12 @@ vi.mock('../src/welcome-backend.ts', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
function configureTestAuthPopup(allow: boolean): void {
|
||||
const directory = join(harness.app.getPath('userData'), 'desktop')
|
||||
mkdirSync(directory, { recursive: true })
|
||||
writeFileSync(join(directory, 'settings.json'), JSON.stringify({ updates: { allowTestAuthPopupWindow: allow } }))
|
||||
}
|
||||
|
||||
function invoke(channel: string, origin = channel === DESKTOP_IPC.boot ? 'app' : 'shell', ...args: unknown[]): unknown {
|
||||
const handler = harness.handlers.get(channel)
|
||||
if (handler === undefined) throw new Error(`missing handler ${channel}`)
|
||||
@@ -437,6 +443,19 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('desktop main startup', () => {
|
||||
it('starts the Host with authentication popups disabled when local settings are invalid', async () => {
|
||||
configureTestAuthPopup(false)
|
||||
const path = join(harness.app.getPath('userData'), 'desktop', 'settings.json')
|
||||
const raw = '{"updates":{"allowTestAuthPopupWindow":"true"}}'
|
||||
writeFileSync(path, raw)
|
||||
await readyForUpdate()
|
||||
expect(harness.hosts).toHaveLength(1)
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining(path), expect.any(Error))
|
||||
expect(harness.dialog.showMessageBox).not.toHaveBeenCalledWith(expect.objectContaining({ title: en.startupFailed }))
|
||||
expect(readFileSync(path, 'utf8')).toBe(raw)
|
||||
})
|
||||
|
||||
it('routes shell update documents and assets through the registered main protocol handler', async () => {
|
||||
const root = join(import.meta.dirname, '..')
|
||||
vi.spyOn(harness.app, 'getAppPath').mockReturnValue(root)
|
||||
@@ -531,7 +550,98 @@ describe('desktop main startup', () => {
|
||||
expect(console.error).toHaveBeenLastCalledWith(expect.objectContaining({ message: 'overlay unavailable' }))
|
||||
})
|
||||
|
||||
it.each(['missing-file', 'missing-field', 'disabled'] as const)('suppresses every test auth dialog for %s while continuing update checks', async (configuration) => {
|
||||
if (configuration === 'disabled') configureTestAuthPopup(false)
|
||||
if (configuration === 'missing-field') {
|
||||
configureTestAuthPopup(true)
|
||||
writeFileSync(join(harness.app.getPath('userData'), 'desktop', 'settings.json'), '{}')
|
||||
}
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))
|
||||
vi.stubGlobal('fetch', request)
|
||||
await readyForUpdate()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
expect(harness.dialog.showMessageBox).not.toHaveBeenCalled()
|
||||
expect(request).toHaveBeenCalledWith(expect.any(URL), expect.objectContaining({ credentials: 'include', redirect: 'error' }))
|
||||
// Editing the file cannot change the consent policy of an already running process.
|
||||
configureTestAuthPopup(true)
|
||||
harness.dialog.showMessageBox.mockResolvedValue({ response: 0 })
|
||||
await invoke(DESKTOP_IPC.updatesOpen, 'app')
|
||||
harness.powerMonitor.emit('resume')
|
||||
await vi.advanceTimersByTimeAsync(10_000)
|
||||
expect(request.mock.calls.length).toBeGreaterThan(2)
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
expect(testAuth.focus).not.toHaveBeenCalled()
|
||||
const messages = harness.dialog.showMessageBox.mock.calls.map(call => (call.at(-1) as { message: string }).message)
|
||||
const expected = JSON.parse(readFileSync(new URL('./expected/policy-login-en.json', import.meta.url), 'utf8')) as Record<string, string[]>
|
||||
expect(messages).toEqual(expected.disabled)
|
||||
expect(harness.updateDownload).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('checks updates from the application menu without offering disabled test authentication', async () => {
|
||||
configureTestAuthPopup(false)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test',
|
||||
allowedAuthOrigins: ['https://login.example.com'], allowedPageOrigins: ['https://downloads.example.com'] }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))
|
||||
vi.stubGlobal('fetch', request)
|
||||
await readyForUpdate()
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
request.mockClear()
|
||||
harness.updateCheck.mockClear()
|
||||
const checkUpdates = applicationMenuItems().find(item => item.label === en.checkUpdatesMenu)!.click as () => void
|
||||
checkUpdates()
|
||||
await vi.advanceTimersByTimeAsync(10_000)
|
||||
expect(request).toHaveBeenCalled()
|
||||
expect(harness.updateCheck).toHaveBeenCalledWith(true)
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
expect(harness.dialog.showMessageBox.mock.calls.map(call => (call.at(-1) as { message: string }).message))
|
||||
.not.toContain(en.policyLoginRequired)
|
||||
})
|
||||
|
||||
it.each([false, true])('retains anonymous production checks with popup permission %s', async (allow) => {
|
||||
configureTestAuthPopup(allow)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'anonymous',
|
||||
allowedPageOrigins: ['https://downloads.example.com'] }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))
|
||||
vi.stubGlobal('fetch', request)
|
||||
await readyForUpdate()
|
||||
await invoke(DESKTOP_IPC.updatesOpen, 'app')
|
||||
expect(request).toHaveBeenCalledWith(expect.any(URL), expect.objectContaining({ credentials: 'omit' }))
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
expect(harness.dialog.showMessageBox.mock.calls.map(call => (call.at(-1) as { message: string }).message))
|
||||
.not.toContain(en.policyLoginRequired)
|
||||
})
|
||||
|
||||
it('keeps a received mandatory decision when test cookies expire with popups disabled', async () => {
|
||||
configureTestAuthPopup(false)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))
|
||||
.mockResolvedValueOnce(Response.json({ code: 40005, data: { show_content: { title: 'Update', detail: 'Required' },
|
||||
desktop_app_link: 'https://downloads.example.com/' } }))
|
||||
vi.stubGlobal('fetch', request)
|
||||
const host = await readyForUpdate()
|
||||
await harness.policyBlocked.promise
|
||||
await vi.advanceTimersByTimeAsync(1000)
|
||||
const modal = harness.windows[0]!
|
||||
const owned = { sender: modal.webContents, senderFrame: modal.webContents.mainFrame }
|
||||
await harness.handlers.get(MANDATORY_IPC.action)!(owned, 'refresh')
|
||||
expect(harness.handlers.get(MANDATORY_IPC.status)!(owned)).toMatchObject({
|
||||
policy: { blocking: true, error: 'authentication-required' },
|
||||
})
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
expect(harness.dialog.showMessageBox).not.toHaveBeenCalled()
|
||||
expect(modal.isDestroyed()).toBe(false)
|
||||
expect(host.stop).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows one explained startup login before Host readiness and joins concurrent checks without reopening it', async () => {
|
||||
configureTestAuthPopup(true)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 }
|
||||
vi.stubGlobal('fetch', vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
@@ -575,6 +685,7 @@ describe('desktop main startup', () => {
|
||||
})
|
||||
|
||||
it.each(['returned', 'cancelled', 'failed'] as const)('requires explicit test login and handles %s without downloading', async (outcome) => {
|
||||
configureTestAuthPopup(true)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 10_000, jitter: 0 }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 }))
|
||||
@@ -602,6 +713,7 @@ describe('desktop main startup', () => {
|
||||
})
|
||||
|
||||
it('does not open Feishu when the user declines test login', async () => {
|
||||
configureTestAuthPopup(true)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'] }
|
||||
vi.stubGlobal('fetch', vi.fn<typeof fetch>().mockImplementation(async () => Response.json({ error: { code: 'UNAUTHENTICATED' } }, { status: 401 })))
|
||||
@@ -625,6 +737,7 @@ describe('desktop main startup', () => {
|
||||
})
|
||||
|
||||
it('retains the embedded block and running Host after expired test login is cancelled', async () => {
|
||||
configureTestAuthPopup(true)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 1000, jitter: 0 }
|
||||
const request = vi.fn<typeof fetch>().mockImplementation(async () =>
|
||||
@@ -1557,7 +1670,8 @@ describe('desktop main startup', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('queues policy authentication until the ordinary result dialog closes', async () => {
|
||||
it.each([false, true])('handles a deferred authentication failure with popup permission %s', async (allow) => {
|
||||
configureTestAuthPopup(allow)
|
||||
harness.embeddedPolicy = { origin: 'https://policy.example.com', authentication: 'feishu-test', allowedAuthOrigins: ['https://login.example.com'],
|
||||
allowedPageOrigins: ['https://downloads.example.com'], intervalMs: 10_000, jitter: 0 }
|
||||
const request = vi.fn<typeof fetch>()
|
||||
@@ -1583,7 +1697,10 @@ describe('desktop main startup', () => {
|
||||
.not.toContain(en.policyLoginRequired)
|
||||
ordinaryResult.resolve({ response: 0 })
|
||||
await operation
|
||||
await policyShown.promise
|
||||
await vi.advanceTimersByTimeAsync(0)
|
||||
if (allow) await policyShown.promise
|
||||
else expect(harness.dialog.showMessageBox.mock.calls.map(call => (call.at(-1) as { message: string }).message))
|
||||
.not.toContain(en.policyLoginRequired)
|
||||
expect(testAuth.login).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import * as fs from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, beforeEach, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { readDesktopSettings } from '../src/settings.ts'
|
||||
|
||||
vi.mock('node:fs', async importOriginal => ({ ...await importOriginal<typeof import('node:fs')>() }))
|
||||
|
||||
beforeEach(() => { vi.spyOn(console, 'warn').mockImplementation(() => {}) })
|
||||
afterEach(() => { vi.restoreAllMocks() })
|
||||
|
||||
function fixture() {
|
||||
const userData = mkdtempSync(join(tmpdir(), 'dsh-desktop-settings-'))
|
||||
onTestFinished(() => { rmSync(userData, { recursive: true, force: true }) })
|
||||
const directory = join(userData, 'desktop')
|
||||
mkdirSync(directory)
|
||||
return { userData, path: join(directory, 'settings.json') }
|
||||
}
|
||||
|
||||
it('creates a discoverable default file on first launch without touching other preferences', () => {
|
||||
const { userData, path } = fixture()
|
||||
rmSync(join(userData, 'desktop'), { recursive: true })
|
||||
writeFileSync(join(userData, 'keybindings.json'), 'existing shortcuts\n')
|
||||
expect(readDesktopSettings(userData)).toEqual({ updates: { allowTestAuthPopupWindow: false } })
|
||||
expect(readFileSync(path, 'utf8')).toBe('{\n "updates": {\n "allowTestAuthPopupWindow": false\n }\n}\n')
|
||||
expect(readFileSync(join(userData, 'keybindings.json'), 'utf8')).toBe('existing shortcuts\n')
|
||||
})
|
||||
|
||||
it.each(['{}', '{"updates": {}}', '{"other": 1, "updates": {"other": true}}'])('defaults an older document without rewriting it: %s', (raw) => {
|
||||
const { userData, path } = fixture()
|
||||
writeFileSync(path, raw)
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(readFileSync(path, 'utf8')).toBe(raw)
|
||||
})
|
||||
|
||||
it('loads explicit opt-in and opt-out on the next read without changing an earlier process snapshot', () => {
|
||||
const { userData, path } = fixture()
|
||||
for (const allow of [true, false]) {
|
||||
const raw = JSON.stringify({ updates: { allowTestAuthPopupWindow: allow }, custom: 'preserve' })
|
||||
writeFileSync(path, raw)
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(allow)
|
||||
expect(readFileSync(path, 'utf8')).toBe(raw)
|
||||
}
|
||||
const previous = readDesktopSettings(userData)
|
||||
writeFileSync(path, '{"updates":{"allowTestAuthPopupWindow":true}}')
|
||||
expect(previous.updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['', '{', 'null', '[]', '{"updates":null}', '{"updates":[]}',
|
||||
'{"updates":{"allowTestAuthPopupWindow":"false"}}', '{"updates":{"allowTestAuthPopupWindow":1}}',
|
||||
'{"updates":{"allowTestAuthPopupWindow":null}}'])('reports invalid settings with their path and preserves the original: %s', (raw) => {
|
||||
const { userData, path } = fixture()
|
||||
writeFileSync(path, raw)
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining(path), expect.any(Error))
|
||||
expect(readFileSync(path, 'utf8')).toBe(raw)
|
||||
})
|
||||
|
||||
it('reports filesystem errors instead of replacing unreadable settings', () => {
|
||||
const { userData, path } = fixture()
|
||||
mkdirSync(path)
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining(path), expect.any(Error))
|
||||
expect(fs.statSync(path).isDirectory()).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['mkdirSync', 'writeFileSync'] as const)('continues with disabled popups when %s fails', (operation) => {
|
||||
const { userData, path } = fixture()
|
||||
const failure = Object.assign(new Error('settings creation denied'), { code: 'EACCES' })
|
||||
vi.spyOn(fs, operation).mockImplementation(() => { throw failure })
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining(path), failure)
|
||||
})
|
||||
|
||||
it('continues with defaults even when warning output fails', () => {
|
||||
const { userData, path } = fixture()
|
||||
writeFileSync(path, '{')
|
||||
vi.mocked(console.warn).mockImplementation(() => { throw new Error('output unavailable') })
|
||||
expect(readDesktopSettings(userData).updates.allowTestAuthPopupWindow).toBe(false)
|
||||
expect(readFileSync(path, 'utf8')).toBe('{')
|
||||
})
|
||||
@@ -102,6 +102,7 @@ vi.mock('electron', () => ({
|
||||
vi.mock('../src/tray.ts', () => ({ DesktopTray: class { relabel() {} dispose() {} } }))
|
||||
|
||||
vi.mock('../src/paths.ts', () => ({ resolveDesktopPaths: () => ({ profile: '/profile' }) }))
|
||||
vi.mock('../src/settings.ts', () => ({ readDesktopSettings: () => ({ updates: { allowTestAuthPopupWindow: false } }) }))
|
||||
vi.mock('../src/login-shell-environment.ts', async importOriginal => ({
|
||||
...await importOriginal<typeof import('../src/login-shell-environment.ts')>(),
|
||||
readDesktopLoginShellEnvironment: async (base: NodeJS.ProcessEnv) => ({ environment: base, failures: [] }),
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
config:
|
||||
maxMembers: 8
|
||||
maxTasks: 256
|
||||
maxPendingMessagesPerMember: 64
|
||||
maxMessageBytes: 65536
|
||||
disposalTimeoutMs: 5000
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ const RUNNING_DRAFT_EXPECTED = join(SNAPSHOT_DIR, 'running-draft.expected.md')
|
||||
const ERROR_EXPECTED = join(SNAPSHOT_DIR, 'error-auth.expected.md')
|
||||
const RETRY_EXPECTED = join(SNAPSHOT_DIR, 'retry.expected.md')
|
||||
const RETRY_EXPANDED_EXPECTED = join(SNAPSHOT_DIR, 'retry-expanded.expected.md')
|
||||
const RETRY_REQUEST_EXPECTED = join(SNAPSHOT_DIR, 'retry-request.expected.md')
|
||||
const RETRY_EXHAUSTED_EXPECTED = join(SNAPSHOT_DIR, 'retry-exhausted.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
const AUTH_PROVIDER_MESSAGE = 'Authentication Fails, Your api key: sk-preview-secret is invalid'
|
||||
@@ -342,11 +343,22 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
||||
// sourced from the recording, never copied into a committed sidecar.
|
||||
{ at: 1, entry: derived[0]! },
|
||||
],
|
||||
}))
|
||||
}), {
|
||||
mode: 'normal', maxRetries: 5, retryableCodes: ['SERVER'],
|
||||
backoff: { initialDelayMs: 25, maxDelayMs: 25, jitterRatio: 0 },
|
||||
})
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-retry'))
|
||||
const releaseFailure = Promise.withResolvers<undefined>()
|
||||
const retryReady = Promise.withResolvers<undefined>()
|
||||
const releaseRetry = Promise.withResolvers<undefined>()
|
||||
const requestStates: string[] = []
|
||||
let attempts = 0
|
||||
// Retirement must follow a visible partial, not race the first browser paint.
|
||||
const stopHolding = scaffold!.ctx.on('llm/stream', async function* (_request, next) {
|
||||
if (++attempts === 2) {
|
||||
retryReady.resolve(undefined)
|
||||
await releaseRetry.promise
|
||||
}
|
||||
for await (const chunk of next()) {
|
||||
yield chunk
|
||||
if (chunk.type === 'text-delta' && chunk.text === RETRY_PARTIAL) await releaseFailure.promise
|
||||
@@ -356,9 +368,20 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
||||
const { settled } = await sendPrompt(60_000)
|
||||
await page.getByText(RETRY_PARTIAL, { exact: true }).waitFor({ timeout: 15_000 })
|
||||
releaseFailure.resolve(undefined)
|
||||
await retryReady.promise
|
||||
await page.getByRole('tab', { name: 'Trajectory', exact: true }).click()
|
||||
await page.getByRole('table').getByRole('button', { name: 'Request #1', exact: true }).click()
|
||||
const overview = page.getByRole('tabpanel').locator('dl').first()
|
||||
await overview.getByText('Pending', { exact: true }).waitFor()
|
||||
await overview.getByText('Last attempt error', { exact: true }).waitFor()
|
||||
expect(await overview.getByText('Error', { exact: true }).count()).toBe(0)
|
||||
requestStates.push('# Retrying request', await overview.ariaSnapshot())
|
||||
await page.getByRole('tab', { name: 'Chat', exact: true }).click()
|
||||
releaseRetry.resolve(undefined)
|
||||
await settled
|
||||
} finally {
|
||||
releaseFailure.resolve(undefined)
|
||||
releaseRetry.resolve(undefined)
|
||||
stopHolding()
|
||||
}
|
||||
expect(turnEndReasons(sessionEvents).at(-1)).toBe('completed')
|
||||
@@ -379,6 +402,14 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
||||
scaffold!.workspaceCwd,
|
||||
)
|
||||
await compareOrRefreshGolden(RETRY_EXPANDED_EXPECTED, expanded, MODE)
|
||||
await page.getByRole('tab', { name: 'Trajectory', exact: true }).click()
|
||||
await page.getByRole('table').getByRole('button', { name: 'Request #1', exact: true }).click()
|
||||
const overview = page.getByRole('tabpanel').locator('dl').first()
|
||||
await overview.getByText('Completed', { exact: true }).waitFor()
|
||||
await overview.getByText('Last attempt error', { exact: true }).waitFor()
|
||||
expect(await overview.getByText('Error', { exact: true }).count()).toBe(0)
|
||||
requestStates.push('# Recovered request', await overview.ariaSnapshot())
|
||||
await compareOrRefreshGolden(RETRY_REQUEST_EXPECTED, requestStates.join('\n\n'), MODE)
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
}, 120_000)
|
||||
@@ -420,7 +451,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'session.v3.jsonl', 'cancel.expected.md', 'cancel-expanded.expected.md',
|
||||
'loading.expected.md', 'running-draft.expected.md', 'error-auth.expected.md',
|
||||
'retry.expected.md', 'retry-expanded.expected.md', 'retry-exhausted.expected.md',
|
||||
'retry.expected.md', 'retry-expanded.expected.md', 'retry-request.expected.md', 'retry-exhausted.expected.md',
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -100,10 +100,10 @@ describe.skipIf(MODE === 'record')('web snapshot: Official native subagent bundl
|
||||
})
|
||||
expect((await scaffold.ctx.pluginManager.listBundles()).find(bundle => bundle.name === BUNDLE))
|
||||
.toMatchObject({ official: true, availability: 'profile', enabled: false })
|
||||
expect((await scaffold.ctx.agentPresets.compositionInventory()).find(preset => preset.id === 'ptc')?.rows
|
||||
.some(row => row.entryId === 'tool-subagent-claude-code')).toBe(false)
|
||||
expect(scaffold.ctx.tools.schemas().some(tool => tool.name === 'subagent_claude_code')).toBe(false)
|
||||
expect(await scaffold.ctx.pluginManager.setBundleEnabled(BUNDLE, true))
|
||||
.toMatchObject({ application: 'applied', changed: true })
|
||||
expect(scaffold.ctx.tools.schemas().some(tool => tool.name === 'subagent_claude_code')).toBe(true)
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
tripwire = watchConsole(page)
|
||||
|
||||
@@ -18,6 +18,7 @@ const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/ui.e
|
||||
const TRAJECTORY_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/trajectory.expected.md', import.meta.url))
|
||||
const CODE_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/code.expected.md', import.meta.url))
|
||||
const PREPARING_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/preparing.expected.md', import.meta.url))
|
||||
const REQUEST_LIFECYCLE_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/ptc-round/request-lifecycle.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
// Elicits the successful and failed sub-rows this scenario asserts.
|
||||
@@ -55,6 +56,7 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
|
||||
|
||||
it('drives the recorded prompt to a settled turn (all modes)', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-ptc-drive'))
|
||||
const requestStates: string[] = []
|
||||
if (MODE !== 'record') {
|
||||
expect(fixtureUserPrompts(await readFile(FIXTURE, 'utf8'))).toEqual([PROMPT])
|
||||
}
|
||||
@@ -62,7 +64,11 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
|
||||
await input.waitFor({ timeout: 10_000 })
|
||||
const settled = scaffold.whenTurnSettled()
|
||||
const gate = Promise.withResolvers<undefined>()
|
||||
const waiting = Promise.withResolvers<undefined>()
|
||||
const start = Promise.withResolvers<undefined>()
|
||||
const dispose = scaffold.ctx.on('llm/stream', async function* (_options, next) {
|
||||
waiting.resolve(undefined)
|
||||
await start.promise
|
||||
let held = false
|
||||
for await (const chunk of next()) {
|
||||
yield chunk
|
||||
@@ -72,11 +78,21 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
|
||||
}
|
||||
}
|
||||
}, { prepend: true })
|
||||
releasePreparation = () => { gate.resolve(undefined); dispose() }
|
||||
releasePreparation = () => { start.resolve(undefined); gate.resolve(undefined); dispose() }
|
||||
const observePreparation = async () => {
|
||||
try {
|
||||
await input.fill(PROMPT)
|
||||
await input.press('Enter')
|
||||
await waiting.promise
|
||||
await page.getByRole('tab', { name: 'Trajectory', exact: true }).click()
|
||||
const request = page.getByRole('table').getByRole('button', { name: 'Request #1', exact: true })
|
||||
await request.click()
|
||||
const panel = page.getByRole('tabpanel')
|
||||
await panel.locator('dl').first().getByText('Pending', { exact: true }).waitFor()
|
||||
expect(await panel.getByRole('button', { name: 'Assistant Message', exact: true }).count()).toBe(0)
|
||||
requestStates.push('# Waiting for output', await panel.locator('dl').first().ariaSnapshot())
|
||||
await page.getByRole('tab', { name: 'Chat', exact: true }).click()
|
||||
start.resolve(undefined)
|
||||
const row = page.locator('[data-chat-call-id] [data-state="preparing"]').first()
|
||||
await row.waitFor({ state: 'attached', timeout: 30_000 })
|
||||
await expandOwningTurnProcess(page, row)
|
||||
@@ -88,14 +104,45 @@ describe('web e2e: PTC mode round renders nested sub-calls', () => {
|
||||
expect(await row.locator('pre').count()).toBe(0)
|
||||
const group = row.locator('xpath=ancestor::*[@data-chat-group-key][1]')
|
||||
await compareOrRefreshGolden(PREPARING_EXPECTED, await group.ariaSnapshot(), MODE)
|
||||
await page.getByRole('tab', { name: 'Trajectory', exact: true }).click()
|
||||
const assistant = page.locator('tr[data-kind="message"][data-running="true"]').first()
|
||||
await assistant.click()
|
||||
await panel.locator('dl').first().getByText('Pending', { exact: true }).waitFor()
|
||||
expect(await panel.getByText('Completed', { exact: true }).count()).toBe(0)
|
||||
requestStates.push('# Streaming assistant', await panel.locator('dl').first().ariaSnapshot())
|
||||
await panel.getByRole('button', { name: 'Request Timing', exact: true }).click()
|
||||
expect(await panel.getByText('Not recorded', { exact: true }).count()).toBe(0)
|
||||
expect(await panel.locator('dt').filter({ hasText: /^Started$/ }).locator('..').getByRole('button').count()).toBe(1)
|
||||
await request.click()
|
||||
await panel.locator('dl').first().getByText('Pending', { exact: true }).waitFor()
|
||||
requestStates.push('# Streaming request', await panel.locator('dl').first().ariaSnapshot())
|
||||
await page.getByRole('tab', { name: 'Chat', exact: true }).click()
|
||||
} finally {
|
||||
releasePreparation?.()
|
||||
}
|
||||
}
|
||||
const [sessionId] = await Promise.all([settled, observePreparation()])
|
||||
await expect.poll(() => page.locator('[data-state="preparing"]').count(), { timeout: 15_000 }).toBe(0)
|
||||
if (MODE === 'record') {
|
||||
await recordFixture(scaffold, sessionId, FIXTURE)
|
||||
try {
|
||||
const [sessionId] = await Promise.all([settled, observePreparation()])
|
||||
expect(sessionEvents.filter(event => event.type === 'turn/end')).toMatchObject([
|
||||
{ data: { reason: { kind: 'completed' } } },
|
||||
])
|
||||
await expect.poll(() => page.locator('[data-state="preparing"]').count(), { timeout: 15_000 }).toBe(0)
|
||||
if (MODE === 'record') {
|
||||
await recordFixture(scaffold, sessionId, FIXTURE)
|
||||
} else {
|
||||
await page.getByRole('tab', { name: 'Trajectory', exact: true }).click()
|
||||
await page.getByRole('table').getByRole('button', { name: 'Request #1', exact: true }).click()
|
||||
const panel = page.getByRole('tabpanel')
|
||||
await panel.getByText('Completed', { exact: true }).waitFor()
|
||||
requestStates.push('# Settled request', await panel.locator('dl').first().ariaSnapshot())
|
||||
await panel.getByRole('button', { name: 'Assistant Message', exact: true }).click()
|
||||
await panel.getByText('Completed', { exact: true }).waitFor()
|
||||
requestStates.push('# Settled assistant', await panel.locator('dl').first().ariaSnapshot())
|
||||
await compareOrRefreshGolden(REQUEST_LIFECYCLE_EXPECTED, requestStates.join('\n\n'), MODE)
|
||||
}
|
||||
} finally {
|
||||
releasePreparation?.()
|
||||
await page.getByRole('tab', { name: 'Chat', exact: true }).click()
|
||||
}
|
||||
}, 200_000)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** A standalone tool in a custom scoped patch reaches the PTC SDK and a persisted Session. */
|
||||
/** A standalone global tool from a custom Host patch reaches the PTC SDK and a persisted Session. */
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -32,8 +32,7 @@ describe('web snapshot: standalone string editor through PTC', () => {
|
||||
compareReplaySession: true,
|
||||
...(MODE === 'record' ? {} : { replayFixture: FIXTURE }),
|
||||
})
|
||||
expect((await scaffold.ctx.agentPresets.compositionInventory()).find(preset => preset.id === 'ptc')?.rows
|
||||
.some(row => row.moduleName === '@deepseek-ai/dsh-tool-str-replace-editor')).toBe(true)
|
||||
expect(scaffold.ctx.tools.schemas().some(tool => tool.name === 'str_replace_editor')).toBe(true)
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
tripwire = watchConsole(page)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
- preset: preset-ptc
|
||||
insert:
|
||||
- insert:
|
||||
- id: optional-tool-str-replace-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 20ef882e44b06a10
|
||||
zh: c10c3934ed5570e9
|
||||
/deepseek-harness-architecture/events:
|
||||
en: 214c3c81c21df6f9
|
||||
zh: 75d2cdc3c9fc9930
|
||||
en: e89802f066e4955d
|
||||
zh: ea2fb4bef94e086d
|
||||
/deepseek-harness-architecture/turn-flow:
|
||||
en: e6402b28c0ed15fa
|
||||
zh: 6c07be9047b42056
|
||||
@@ -30,8 +30,8 @@
|
||||
en: a2e35c43438fec6d
|
||||
zh: e27bdac8e9feff32
|
||||
/deepseek-harness-architecture/capability-seams:
|
||||
en: 93d1dc2eeda4aec2
|
||||
zh: ee7a9da89151ea17
|
||||
en: a5bc20c3f2a1b370
|
||||
zh: 91773a281410b2f4
|
||||
/deepseek-harness-architecture/where-new-behavior-goes:
|
||||
en: 9b2eb0539a9cd0d6
|
||||
zh: 4f575277b91d4e5c
|
||||
@@ -72,7 +72,7 @@ Events are the extension points, and picking the right domain is the first decis
|
||||
- **Agent events** (`agent/*`) carry a live `Agent`: inbox, step, status, request, validation, continuation. Use one to observe or intercept work in flight.
|
||||
- **Capability events** attach policy and adapters to a seam (`fs/*`, `tools/*`, `telemetry/*`) without importing the loop.
|
||||
|
||||
AgentLoop awaits serial `agent/created` initialization before starting queued work. Initialization failure rolls back creation; [agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) defines teardown ordering.
|
||||
AgentLoop awaits serial `agent/created` before queued work and rolls back failed initialization. Unload retains resources through Agent teardown; [agent-loop](../packages/core/agent-loop/README.md#understand-the-implementation) defines ordering.
|
||||
|
||||
The [event map](event-producer-consumer.md) lists every event's producers and consumers.
|
||||
|
||||
@@ -133,7 +133,7 @@ A **seam** is a swappable capability with three roles: a **Service Definition**
|
||||
|
||||
Seams are why one provider swap changes the whole product. Filesystem and subprocess providers share one execution world, so pointing them at a remote sandbox moves Bash, PTY, and LSP with them, with no provider forks. [Subagent providers](subsystems/subagent.md) vary just as widely behind one interface, from a fresh child agent to a delegated turn in another product.
|
||||
|
||||
[Experimental Agent Teams](subsystems/agent-team.md) is a published opt-in coordination seam on `ctx.agentTeams`, with a durable roster, task board, and mailbox layered over continuable subagents.
|
||||
[Experimental Agent Teams](subsystems/agent-team.md) exposes opt-in `ctx.agentTeams` coordination with durable roster and task state plus direct inbox messaging over continuable subagents.
|
||||
|
||||
## Where new behavior goes
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共
|
||||
- **Agent 事件**(`agent/*`)携带活跃 `Agent`:inbox、步骤、状态、请求、验证、续跑。要观察或拦截进行中的工作时,使用它。
|
||||
- **能力事件**无需导入循环即可向某个 seam(`fs/*`、`tools/*`、`telemetry/*`)附加策略和适配器。
|
||||
|
||||
AgentLoop 在启动已排队工作前等待串行 `agent/created` 初始化。初始化失败会回滚创建;[agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation)定义 teardown 顺序。
|
||||
AgentLoop 在启动已排队工作前等待串行 `agent/created`,初始化失败则回滚。卸载在 Agent teardown 期间保留所需资源;[agent-loop](../packages/core/agent-loop/README.zh.md#understand-the-implementation)定义顺序。
|
||||
|
||||
[事件映射](event-producer-consumer.zh.md)列出每个事件的生产方与消费方。
|
||||
|
||||
@@ -137,7 +137,7 @@ Session 消费方只了解当前逻辑格式。仅 header 的 `stat` 与 `list`
|
||||
|
||||
seam 正是替换一个提供方就能改变整个产品的原因。文件系统与进程提供方共享同一个执行世界,因此把它们指向远程沙箱,也就把 Bash、PTY 和 LSP 一并搬了过去,无需提供方专用 fork。[subagent 提供方](subsystems/subagent.zh.md)在同一个接口之后同样千差万别,从新建一个子 agent,到把一个轮次委派给另一个产品。
|
||||
|
||||
[实验性 Agent Teams](subsystems/agent-team.zh.md) 是 `ctx.agentTeams` 上公开发布、显式启用的协作 seam,在可继续 subagent 之上提供持久 roster、任务板和 mailbox。
|
||||
[实验性 Agent Teams](subsystems/agent-team.zh.md) 通过显式启用的 `ctx.agentTeams`,在可继续 subagent 之上提供持久 roster、任务状态和直接 inbox 消息协作。
|
||||
|
||||
## 新行为的归属位置
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
en: 41d772192075e133
|
||||
zh: 5ef3da69b4de1d8a
|
||||
/capability-seams-and-core-services:
|
||||
en: 5bdd69045ebd8e3c
|
||||
zh: f791f38cf578f9c9
|
||||
en: 912784044de3337d
|
||||
zh: 6a03f65751cda74a
|
||||
@@ -674,7 +674,7 @@ flowchart LR
|
||||
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | Providers prepare local children or execute external tasks; one activation manager owns both lifecycles. The delegation tool selects a backend, control tools deliver local follow-ups, and workflows await activation results. |
|
||||
| `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | Routes explicit recognizers; the browser uses the authenticated Remote and keeps transcripts in the draft until submission. |
|
||||
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, durable peer mailbox, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls. |
|
||||
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the implicit-root roster, direct peer inbox delivery, shared task DAG, and continuable-child lifecycle; tool-agent-team contributes model controls. |
|
||||
| `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | Loads mods that defineMod plugins add, raises their hook chains from harness extension points, and draws the band above the prompt over its Remote. |
|
||||
| `ctx.inspector` | `core` | `inspector` | - | - | - | Owns the Worker-hosted CDP target and the transport-independent Host and Client observation and Cordis-tree query API. |
|
||||
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | Producers (background bash/pwsh and PTY sends) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. |
|
||||
|
||||
@@ -676,7 +676,7 @@ flowchart LR
|
||||
| `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 |
|
||||
| `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | 提供方准备本地子级或执行外部任务;一个 activation 管理器持有两类生命周期。委派工具选择后端,控制工具投递本地后续消息,工作流等待 activation 结果。 |
|
||||
| `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | 路由显式选择的识别器;浏览器使用带认证的 Remote,并在提交前将转写保留在草稿中。 |
|
||||
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、持久 peer mailbox、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供模型控制工具。 |
|
||||
| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责隐式 Root roster、直接 peer inbox 投递、共享任务 DAG 与 continuable child 生命周期;tool-agent-team 提供模型控制工具。 |
|
||||
| `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | 加载 defineMod 插件添加的模组,从 harness 扩展点触发其钩子链,并通过其 Remote 绘制提示框上方的横幅。 |
|
||||
| `ctx.inspector` | `core` | `inspector` | - | - | - | 负责 Worker 托管的 CDP target,以及独立于传输的 Host 和 Client observation 与 Cordis tree query API。 |
|
||||
| `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | 生产方(后台 bash/pwsh 与 PTY 发送)登记正在运行的工作;声明 record 的 job 还为非消费观察者流式提供原始输出;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 |
|
||||
|
||||
@@ -92,7 +92,7 @@ export interface Config {
|
||||
|
||||
- `inject`: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
|
||||
- `refs`: [`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/core.md) · `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/agent-loop/src/index.ts:292`](../packages/core/agent-loop/src/index.ts)
|
||||
- `source`: [`packages/core/agent-loop/src/index.ts:303`](../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Agent-loop plugin configuration. */
|
||||
@@ -139,7 +139,7 @@ export type Config = PresetDefinition
|
||||
|
||||
- `inject`: `loader` · `sessionProjections`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
|
||||
- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:15`](../packages/preset/agent-preset-registry/src/preset.ts)
|
||||
- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:13`](../packages/preset/agent-preset-registry/src/preset.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Registry selection policy. */
|
||||
@@ -757,7 +757,7 @@ export interface ToolResultPruneConfig {
|
||||
## `@deepseek-ai/dsh-cordis-host-runner`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `source`: [`packages/extensions/cordis-host-runner/src/index.ts:93`](../packages/extensions/cordis-host-runner/src/index.ts)
|
||||
- `source`: [`packages/extensions/cordis-host-runner/src/index.ts:103`](../packages/extensions/cordis-host-runner/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Runner configuration. */
|
||||
@@ -839,7 +839,7 @@ export interface Config {
|
||||
## `@deepseek-ai/dsh-experimental-agent-team`
|
||||
|
||||
- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents`
|
||||
- `source`: [`packages/experimental/agent-team/src/types.ts:152`](../packages/experimental/agent-team/src/types.ts)
|
||||
- `source`: [`packages/experimental/agent-team/src/types.ts:153`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Team-service deployment limits. */
|
||||
@@ -848,11 +848,9 @@ export interface Config {
|
||||
readonly maxMembers?: number
|
||||
/** Maximum non-deleted tasks retained by one Team. */
|
||||
readonly maxTasks?: number
|
||||
/** Maximum queued-minus-delivered messages for one target member. */
|
||||
readonly maxPendingMessagesPerMember?: number
|
||||
/** Maximum UTF-8 bytes in one complete sender-framed delivery. */
|
||||
readonly maxMessageBytes?: number
|
||||
/** Maximum milliseconds allowed for Team-owned runtime disposal. */
|
||||
/** Maximum milliseconds for shared operation settlement and for each Team child drain. */
|
||||
readonly disposalTimeoutMs?: number
|
||||
}
|
||||
```
|
||||
@@ -2362,7 +2360,7 @@ export interface PlanModeConfig {
|
||||
## `@deepseek-ai/dsh-plugin-manager`
|
||||
|
||||
- `inject`: `loader` · `profileContext`
|
||||
- `source`: [`packages/boot/plugin-manager/src/index.ts:44`](../packages/boot/plugin-manager/src/index.ts)
|
||||
- `source`: [`packages/boot/plugin-manager/src/index.ts:42`](../packages/boot/plugin-manager/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** The pnpm executable, registries, and limits for diagnostics, lookups and connection checks. */
|
||||
|
||||
@@ -94,7 +94,7 @@ export interface Config {
|
||||
|
||||
- `inject`: `agents` · `sessions` · `llm` · `tools` · `systemPrompt` · `sessionProjections`
|
||||
- `refs`: [`AgentOptions`](subsystems/core.zh.md) · [`SessionId`](subsystems/core.zh.md) · `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/agent-loop/src/index.ts:292`](../packages/core/agent-loop/src/index.ts)
|
||||
- `source`: [`packages/core/agent-loop/src/index.ts:303`](../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Agent-loop plugin configuration. */
|
||||
@@ -141,7 +141,7 @@ export type Config = PresetDefinition
|
||||
|
||||
- `inject`: `loader` · `sessionProjections`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cordis`)
|
||||
- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:15`](../packages/preset/agent-preset-registry/src/preset.ts)
|
||||
- `source`: [`packages/preset/agent-preset-registry/src/preset.ts:13`](../packages/preset/agent-preset-registry/src/preset.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Registry selection policy. */
|
||||
@@ -759,7 +759,7 @@ export interface ToolResultPruneConfig {
|
||||
## `@deepseek-ai/dsh-cordis-host-runner`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `source`: [`packages/extensions/cordis-host-runner/src/index.ts:93`](../packages/extensions/cordis-host-runner/src/index.ts)
|
||||
- `source`: [`packages/extensions/cordis-host-runner/src/index.ts:103`](../packages/extensions/cordis-host-runner/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Runner configuration. */
|
||||
@@ -841,7 +841,7 @@ export interface Config {
|
||||
## `@deepseek-ai/dsh-experimental-agent-team`
|
||||
|
||||
- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents`
|
||||
- `source`: [`packages/experimental/agent-team/src/types.ts:152`](../packages/experimental/agent-team/src/types.ts)
|
||||
- `source`: [`packages/experimental/agent-team/src/types.ts:153`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Team-service deployment limits. */
|
||||
@@ -850,11 +850,9 @@ export interface Config {
|
||||
readonly maxMembers?: number
|
||||
/** Maximum non-deleted tasks retained by one Team. */
|
||||
readonly maxTasks?: number
|
||||
/** Maximum queued-minus-delivered messages for one target member. */
|
||||
readonly maxPendingMessagesPerMember?: number
|
||||
/** Maximum UTF-8 bytes in one complete sender-framed delivery. */
|
||||
readonly maxMessageBytes?: number
|
||||
/** Maximum milliseconds allowed for Team-owned runtime disposal. */
|
||||
/** Maximum milliseconds for shared operation settlement and for each Team child drain. */
|
||||
readonly disposalTimeoutMs?: number
|
||||
}
|
||||
```
|
||||
@@ -2364,7 +2362,7 @@ export interface PlanModeConfig {
|
||||
## `@deepseek-ai/dsh-plugin-manager`
|
||||
|
||||
- `inject`: `loader` · `profileContext`
|
||||
- `source`: [`packages/boot/plugin-manager/src/index.ts:44`](../packages/boot/plugin-manager/src/index.ts)
|
||||
- `source`: [`packages/boot/plugin-manager/src/index.ts:42`](../packages/boot/plugin-manager/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** The pnpm executable, registries, and limits for diagnostics, lookups and connection checks. */
|
||||
|
||||
@@ -8,7 +8,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
<!-- BEGIN GENERATED event-producer-consumer:events -->
|
||||
| Event | Mode | Declared in | Dispatchers | Listeners |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:249`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:69`](../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-team`, `browser-use-runtime`, `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
@@ -27,7 +27,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:623`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:602`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:609`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:56`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:53`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../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`) | - |
|
||||
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
|
||||
@@ -56,9 +56,9 @@ This matrix shows which packages dispatch each harness-owned event and which pac
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:270`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:277`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:258`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:271`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:478`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` |
|
||||
| `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<!-- BEGIN GENERATED event-producer-consumer:events -->
|
||||
| Event | Mode | Declared in | Dispatchers | Listeners |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:238`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:249`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:69`](../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-team`, `browser-use-runtime`, `claude-code-mods`, [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`loader-smoke`](../packages/test-support/loader-smoke), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) |
|
||||
@@ -29,7 +29,7 @@
|
||||
| `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:623`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/removed` | `emit` | [`packages/api/session-controller/src/types.ts:602`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `api-session/status` | `emit` | [`packages/api/session-controller/src/types.ts:609`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:56`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `app-boot/config-reload` | `emit` | [`packages/boot/app-boot/src/index.ts:53`](../packages/boot/app-boot/src/index.ts) | [`app-boot`](../packages/boot/app-boot) (`emit`) | [`settings`](../packages/settings/settings) |
|
||||
| `approval/request` | `waterfall` | [`packages/interaction/user-approval/src/types.ts:87`](../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`) | - |
|
||||
| `commands/change` | `emit` | [`packages/interaction/commands/src/types.ts:89`](../packages/interaction/commands/src/types.ts) | [`commands`](../packages/interaction/commands) (`events.dispatch`) | `remotes` |
|
||||
@@ -58,9 +58,9 @@
|
||||
| `llm/adapters-updated` | `emit` | [`packages/llm/llm/src/types.ts:23`](../packages/llm/llm/src/types.ts) | [`llm`](../packages/llm/llm) (`events.dispatch`) | [`acp`](../packages/acp/acp), `remotes` |
|
||||
| `llm/stream` | `waterfall` | [`packages/llm/llm/src/index.ts:75`](../packages/llm/llm/src/index.ts) | [`llm`](../packages/llm/llm) (`waterfall`) | [`llm-replay`](../packages/test-support/llm-replay), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), [`session-title`](../packages/session/session-title) |
|
||||
| `permission-presets/catalog-changed` | `emit` | [`packages/interaction/permission-presets/src/types.ts:48`](../packages/interaction/permission-presets/src/types.ts) | [`permission-presets`](../packages/interaction/permission-presets) (`events.dispatch`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:270`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:277`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/changed` | `emit` | [`packages/boot/plugin-manager/src/types.ts:258`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-log` | `emit` | [`packages/boot/plugin-manager/src/types.ts:264`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `plugin-manager/install-state` | `emit` | [`packages/boot/plugin-manager/src/types.ts:271`](../packages/boot/plugin-manager/src/types.ts) | [`plugin-manager`](../packages/boot/plugin-manager) (`emit`) | `remotes` |
|
||||
| `schedule/changed` | `emit` | [`packages/schedule/schedule/src/types.ts:478`](../packages/schedule/schedule/src/types.ts) | [`schedule`](../packages/schedule/schedule) (`emit`) | `remotes` |
|
||||
| `session-telemetry/record` | `waterfall` | [`packages/session/session-telemetry/src/index.ts:44`](../packages/session/session-telemetry/src/index.ts) | [`session-telemetry`](../packages/session/session-telemetry) (`waterfall`) | - |
|
||||
| `session/created` | `emit` | [`packages/core/session/src/index.ts:54`](../packages/core/session/src/index.ts) | [`session`](../packages/core/session) (`events.dispatch`) | [`permission-presets`](../packages/interaction/permission-presets), [`schedule`](../packages/schedule/schedule), `server`, `session-controller`, [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-telemetry`](../packages/session/session-telemetry) |
|
||||
|
||||
@@ -1159,7 +1159,6 @@ flowchart TD
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_plugin_manager --> pkg_agent_preset_registry
|
||||
pkg_plugin_manager --> pkg_brand
|
||||
pkg_plugin_manager --> pkg_hmr
|
||||
pkg_plugin_manager --> pkg_host_plugin_inventory
|
||||
@@ -1729,7 +1728,7 @@ flowchart TD
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
|
||||
| [`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) |
|
||||
| [`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) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) |
|
||||
|
||||
@@ -1161,7 +1161,6 @@ flowchart TD
|
||||
pkg_acp --> pkg_session_persistence
|
||||
pkg_acp --> pkg_token_meter
|
||||
pkg_acp --> pkg_user_approval
|
||||
pkg_plugin_manager --> pkg_agent_preset_registry
|
||||
pkg_plugin_manager --> pkg_brand
|
||||
pkg_plugin_manager --> pkg_hmr
|
||||
pkg_plugin_manager --> pkg_host_plugin_inventory
|
||||
@@ -1731,7 +1730,7 @@ flowchart TD
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
|
||||
| [`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) |
|
||||
| [`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) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner) | `compaction` | [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) |
|
||||
| [`experimental-browser-use-runtime`](../packages/experimental/browser-use-runtime) | `experimental` | [`agent`](../packages/core/agent), [`browser-use`](../packages/browser-use/browser-use), [`mcp-client`](../packages/mcp/mcp-client), [`scope`](../packages/core/scope), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | `experimental` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subprocess`](../packages/subprocess/subprocess), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-questions`](../packages/interaction/user-questions), [`working-directory`](../packages/session/working-directory) |
|
||||
|
||||
@@ -216,17 +216,17 @@
|
||||
en: a640800a38b3c874
|
||||
zh: a640800a38b3c874
|
||||
/session-persistence-event-catalog/events/team/team-member-log-only:
|
||||
en: 7f6c8a45dc35091f
|
||||
zh: 47060fd1344a5ea3
|
||||
en: a2fe5b8582ef5f46
|
||||
zh: 42bfffcde3f3d886
|
||||
/session-persistence-event-catalog/events/team/team-message-delivered-log-only:
|
||||
en: 951c0d2655ede111
|
||||
zh: 05d5c77fc656b918
|
||||
en: 4a716b14e33eab18
|
||||
zh: dcde26e4dc40b62e
|
||||
/session-persistence-event-catalog/events/team/team-message-queued-log-only:
|
||||
en: 02ac91734cd032da
|
||||
zh: 91fae1059caa59b4
|
||||
en: 05bf873244099da5
|
||||
zh: 8a0b62912087f4aa
|
||||
/session-persistence-event-catalog/events/team/team-task-log-only:
|
||||
en: ee2efdbe3f7b8978
|
||||
zh: a3829e506d9e81d1
|
||||
en: 5ffeb64225feda2a
|
||||
zh: d58129e189a726fb
|
||||
/session-persistence-event-catalog/events/todo:
|
||||
en: 308d4bb4b6b96e35
|
||||
zh: 308d4bb4b6b96e35
|
||||
@@ -1197,23 +1197,23 @@
|
||||
en: a3eabd0e9fc565dd
|
||||
zh: fa763aefdbc1bdb8
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teammemberphase:
|
||||
en: 5457d6872347310b
|
||||
zh: e0d5946902c3e933
|
||||
en: 2bf8f06fe328ab33
|
||||
zh: ded602f18e0deadf
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teammembersnapshot:
|
||||
en: f6ee72fe168139ff
|
||||
zh: d28586070ab7b118
|
||||
en: 8a37845006830548
|
||||
zh: b8e7caf6189b92df
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teammessagesnapshot:
|
||||
en: 31fb7e4f07b49012
|
||||
zh: 15fd933a217dadee
|
||||
en: 9b94faac43ba068f
|
||||
zh: a83582be6a6a3c1a
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teammessagesource:
|
||||
en: 11bec13073c76f30
|
||||
zh: e90deecb47471c74
|
||||
en: 269f0a4fcbea1c0c
|
||||
zh: 5f2c50c03c951995
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teamtasksnapshot:
|
||||
en: 336a17d99a56859f
|
||||
zh: 51c252b4f4add812
|
||||
en: cfa8230ef5d661b2
|
||||
zh: cb40ce86d6778f09
|
||||
/session-persistence-event-catalog/resolved-persistence-types/teamtaskstatus:
|
||||
en: 498986242f41c79a
|
||||
zh: 4d219bd7d9a3ac7c
|
||||
en: 9bff57038f472770
|
||||
zh: 4eec0e793ae16d53
|
||||
/session-persistence-event-catalog/resolved-persistence-types/textblock:
|
||||
en: c5fcd2cdd37ea387
|
||||
zh: 9c678fbdea0bf25c
|
||||
@@ -1299,8 +1299,8 @@
|
||||
en: 386989934d1bb62e
|
||||
zh: 69b8bc182a5acbd3
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string:
|
||||
en: 251fe0fd6487d610
|
||||
zh: a2cc6acbf4736773
|
||||
en: 90d3fd7202322ca4
|
||||
zh: 88cce532aed9f6d4
|
||||
/session-persistence-event-catalog/resolved-persistence-types/string~2:
|
||||
en: 0089620bf4638263
|
||||
zh: 1922bef1713094bb
|
||||
@@ -1818,17 +1818,17 @@
|
||||
en: afedf4a31a67eb14
|
||||
zh: 98d3bc882f2b61c3
|
||||
/session-persistence-event-catalog/resolved-persistence-types/member-teamid-version:
|
||||
en: a0a3339a63f2bc4f
|
||||
zh: fd6512ef11c36bde
|
||||
en: 82b0989dad6bbfc0
|
||||
zh: 0789b63f1d59b2ed
|
||||
/session-persistence-event-catalog/resolved-persistence-types/message-step-turn:
|
||||
en: 7cc30865737643f2
|
||||
zh: 512c6c81ed10a9b6
|
||||
/session-persistence-event-catalog/resolved-persistence-types/message-teamid-version:
|
||||
en: 7960b689af537cf6
|
||||
zh: e5b044052e009a98
|
||||
en: f778769acb264dd5
|
||||
zh: eba9b81d04e9f57b
|
||||
/session-persistence-event-catalog/resolved-persistence-types/messageid-targetid-teamid-version:
|
||||
en: 22665480d164fdc7
|
||||
zh: aa2c96be4a4fe468
|
||||
en: 91a948ede28ebcb5
|
||||
zh: 8a2b0ac6bb68a09e
|
||||
/session-persistence-event-catalog/resolved-persistence-types/mode-source:
|
||||
en: 3028b222670ce5ad
|
||||
zh: cae4c9437e560d61
|
||||
@@ -1860,8 +1860,8 @@
|
||||
en: 5360af614fe753e3
|
||||
zh: 96427c489cc71de6
|
||||
/session-persistence-event-catalog/resolved-persistence-types/task-teamid-version:
|
||||
en: bc6a4d5b483d4845
|
||||
zh: 839703c23f628d6b
|
||||
en: a2fcd46e54b14120
|
||||
zh: d9a6e0e50bbff9ad
|
||||
/session-persistence-event-catalog/resolved-persistence-types/todos:
|
||||
en: 06e6c1837e2b5d50
|
||||
zh: 37cebfe3e41294ec
|
||||
|
||||
+17
-17
@@ -978,14 +978,14 @@ Source: [`packages/core/session/src/types.ts:330`](../packages/core/session/src/
|
||||
|
||||
Types: [TeamId](subsystems/agent-team.md) · [TeamMemberSnapshot](subsystems/agent-team.md)
|
||||
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:230`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teammessagedelivered--log-only"></a>
|
||||
|
||||
#### `team/message/delivered` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Durable acknowledgement that the target Session recorded the message. */
|
||||
/** Historical acknowledgement that the target Session recorded a queued message. */
|
||||
'team/message/delivered': {
|
||||
version: 2
|
||||
teamId: TeamId
|
||||
@@ -996,20 +996,20 @@ Source: [`packages/experimental/agent-team/src/types.ts:232`](../packages/experi
|
||||
|
||||
Types: [TeamId](subsystems/agent-team.md) · [TeamMessageId](subsystems/agent-team.md)
|
||||
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:238`](../packages/experimental/agent-team/src/types.ts)
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teammessagequeued--log-only"></a>
|
||||
|
||||
#### `team/message/queued` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Durable mailbox enqueue, stored before delivery is attempted. */
|
||||
/** Historical mailbox enqueue; new sends use the target Agent inbox. */
|
||||
'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot }
|
||||
```
|
||||
|
||||
Types: [TeamId](subsystems/agent-team.md) · [TeamMessageSnapshot](subsystems/agent-team.md)
|
||||
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teamtask--log-only"></a>
|
||||
|
||||
@@ -1022,7 +1022,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experi
|
||||
|
||||
Types: [TeamId](subsystems/agent-team.md) · [TeamTaskSnapshot](subsystems/agent-team.md)
|
||||
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
Source: [`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
### `todo/*`
|
||||
|
||||
@@ -4740,7 +4740,7 @@ Sources: [`packages/llm/llm/src/message.ts:35`](../packages/llm/llm/src/message.
|
||||
|
||||
SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:44`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:45`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -4758,7 +4758,7 @@ One of:
|
||||
|
||||
SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:48`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4780,7 +4780,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:47`](../packages/experi
|
||||
|
||||
SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:128`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:129`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4800,7 +4800,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:128`](../packages/exper
|
||||
|
||||
SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:137`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:138`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4820,7 +4820,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:137`](../packages/exper
|
||||
|
||||
SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:74`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:75`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -4843,7 +4843,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:74`](../packages/experi
|
||||
|
||||
SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:71`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:72`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
One of:
|
||||
|
||||
@@ -5382,7 +5382,7 @@ Array of `number`.
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
Sources: [`packages/api/session-controller/src/types.ts:403`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
Sources: [`packages/api/session-controller/src/types.ts:403`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -7492,7 +7492,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:61`](../packages/web
|
||||
|
||||
SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:230`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7520,7 +7520,7 @@ Sources: [`packages/core/session/src/types.ts:330`](../packages/core/session/src
|
||||
|
||||
SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7534,7 +7534,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:236`](../packages/exper
|
||||
|
||||
SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:238`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
@@ -7680,7 +7680,7 @@ Sources: [`packages/compaction/compaction-image-offload/src/projection.ts:25`](.
|
||||
|
||||
SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c`
|
||||
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
Sources: [`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
|
||||
@@ -980,14 +980,14 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMemberSnapshot](subsystems/agent-team.zh.md)
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:230`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teammessagedelivered--log-only"></a>
|
||||
|
||||
#### `team/message/delivered` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Durable acknowledgement that the target Session recorded the message. */
|
||||
/** Historical acknowledgement that the target Session recorded a queued message. */
|
||||
'team/message/delivered': {
|
||||
version: 2
|
||||
teamId: TeamId
|
||||
@@ -998,20 +998,20 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageId](subsystems/agent-team.zh.md)
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:238`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teammessagequeued--log-only"></a>
|
||||
|
||||
#### `team/message/queued` — log-only
|
||||
|
||||
```ts persistence-catalog
|
||||
/** Durable mailbox enqueue, stored before delivery is attempted. */
|
||||
/** Historical mailbox enqueue; new sends use the target Agent inbox. */
|
||||
'team/message/queued': { version: 2; teamId: TeamId; message: TeamMessageSnapshot }
|
||||
```
|
||||
|
||||
类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageSnapshot](subsystems/agent-team.zh.md)
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
<a id="teamtask--log-only"></a>
|
||||
|
||||
@@ -1024,7 +1024,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TeamId](subsystems/agent-team.zh.md) · [TeamTaskSnapshot](subsystems/agent-team.zh.md)
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
### `todo/*`
|
||||
|
||||
@@ -4742,7 +4742,7 @@ SHA-256: `65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad`
|
||||
|
||||
SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:44`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:45`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -4760,7 +4760,7 @@ SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd`
|
||||
|
||||
SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:48`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4782,7 +4782,7 @@ SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25`
|
||||
|
||||
SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:128`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:129`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4802,7 +4802,7 @@ SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e`
|
||||
|
||||
SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:137`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:138`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4822,7 +4822,7 @@ SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0`
|
||||
|
||||
SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:74`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:75`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -4845,7 +4845,7 @@ SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357`
|
||||
|
||||
SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:71`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:72`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
以下类型之一:
|
||||
|
||||
@@ -5384,7 +5384,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
|
||||
|
||||
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
|
||||
|
||||
来源:[`packages/api/session-controller/src/types.ts:403`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
来源:[`packages/api/session-controller/src/types.ts:403`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:21`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:33`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:9`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
|
||||
|
||||
`string`
|
||||
|
||||
@@ -7494,7 +7494,7 @@ SHA-256: `930a6567a10bb62ddd157bd8abdf4b182810c8c3b49d91309b3b009a5fed9731`
|
||||
|
||||
SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:230`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7522,7 +7522,7 @@ SHA-256: `6bc8e51124d0874bc89e1d0ceff381a9c3cd0fc240667d69f841ce4f60f65fe5`
|
||||
|
||||
SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7536,7 +7536,7 @@ SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403`
|
||||
|
||||
SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:238`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:236`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
@@ -7682,7 +7682,7 @@ SHA-256: `477614bca595a9e7adb5a1870be446b9a8684f3ba8950259d6289f2d3b74f028`
|
||||
|
||||
SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c`
|
||||
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:234`](../packages/experimental/agent-team/src/types.ts)
|
||||
来源:[`packages/experimental/agent-team/src/types.ts:232`](../packages/experimental/agent-team/src/types.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 39494aa3af994e6f
|
||||
zh: 07c2880503668176
|
||||
/2026-10-05-working-directory-attribution/verification:
|
||||
en: 5963942c0d87e518
|
||||
zh: 5dbb6ac0fca1f279
|
||||
en: 9b79379292881d10
|
||||
zh: 54d670c09ce1bfc7
|
||||
/2026-10-05-working-directory-attribution/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -52,7 +52,7 @@ Existing records remain valid. The additive working-directory source kind is att
|
||||
<a id="verification"></a>
|
||||
## Verification
|
||||
|
||||
pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts: 4 files and 203 tests passed.
|
||||
pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent/tests/schedule-tools.spec.ts: 5 files and 207 tests passed.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## Dev Note
|
||||
|
||||
@@ -52,7 +52,7 @@ changes:
|
||||
<a id="verification"></a>
|
||||
## 验证
|
||||
|
||||
pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts:4 个文件、203 个测试通过。
|
||||
pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent/tests/schedule-tools.spec.ts:5 个文件、207 个测试通过。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
## 开发备注
|
||||
|
||||
Generated
+13
-13
@@ -22152,7 +22152,7 @@
|
||||
"digest": "0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:238"
|
||||
"packages/experimental/agent-team/src/types.ts:236"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -22532,7 +22532,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamMessageSource"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:137"
|
||||
"packages/experimental/agent-team/src/types.ts:138"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -22707,7 +22707,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:128"
|
||||
"packages/experimental/agent-team/src/types.ts:129"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -23141,9 +23141,9 @@
|
||||
"packages/attachment/attachment/src/brand.ts:6",
|
||||
"packages/compaction/compaction/src/brand.ts:4",
|
||||
"packages/core/session/src/types.ts:20",
|
||||
"packages/experimental/agent-team/src/types.ts:20",
|
||||
"packages/experimental/agent-team/src/types.ts:32",
|
||||
"packages/experimental/agent-team/src/types.ts:8",
|
||||
"packages/experimental/agent-team/src/types.ts:21",
|
||||
"packages/experimental/agent-team/src/types.ts:33",
|
||||
"packages/experimental/agent-team/src/types.ts:9",
|
||||
"packages/experimental/webhook/src/brand.ts:12",
|
||||
"packages/experimental/webhook/src/brand.ts:6",
|
||||
"packages/experimental/webhook/src/brand.ts:9",
|
||||
@@ -23234,7 +23234,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:74"
|
||||
"packages/experimental/agent-team/src/types.ts:75"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -23507,7 +23507,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:47"
|
||||
"packages/experimental/agent-team/src/types.ts:48"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -24417,7 +24417,7 @@
|
||||
"digest": "a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:234"
|
||||
"packages/experimental/agent-team/src/types.ts:232"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -24659,7 +24659,7 @@
|
||||
"digest": "b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:232"
|
||||
"packages/experimental/agent-team/src/types.ts:230"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -24688,7 +24688,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamTaskStatus"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:71"
|
||||
"packages/experimental/agent-team/src/types.ts:72"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -25227,7 +25227,7 @@
|
||||
"packages/experimental/agent-team/src/types.ts#TeamMemberPhase"
|
||||
],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:44"
|
||||
"packages/experimental/agent-team/src/types.ts:45"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -25359,7 +25359,7 @@
|
||||
"digest": "ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"packages/experimental/agent-team/src/types.ts:236"
|
||||
"packages/experimental/agent-team/src/types.ts:234"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/README.md
|
||||
/subsystems:
|
||||
en: 34b6bc8c15b8009b
|
||||
zh: 7a07e5c32b3feca1
|
||||
en: 1d4818dbb07391a7
|
||||
zh: f1e27ce66c181295
|
||||
@@ -50,7 +50,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
|
||||
| [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |
|
||||
| [voice-input.md](voice-input.md) | experimental named recognizers, transient audio and revision-guarded draft insertion |
|
||||
| [translation.md](translation.md) | experimental anonymous text translation and explicit provider requests |
|
||||
| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, durable peer mailbox, and shared task DAG |
|
||||
| [agent-team.md](agent-team.md) | Agent Teams: implicit Lead identity, named continuable teammates, direct peer inbox delivery, and shared task DAG |
|
||||
| [claude-code-mods.md](claude-code-mods.md) | Claude Code mods through the experimental bridge: every event, `$` member, chain rule, drawing feature, and example mod that differs from Claude Code |
|
||||
| [web.md](web.md) | the web access seam: `WebSearchRequest`/`Result`, `WebFetchRequest`/`Result`, `WebFetchBody`, provider availability, `WebError` |
|
||||
| [spill.md](spill.md) | the spill storage seam: `SaveTextSpill`, `SpillOwner`/`SpillSource`, `SpillRef`, the branded `SpillLocator` |
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
| [subagent.md](subagent.zh.md) | subagent seam:命名提供方注册表、`SubagentStartRequest`/`Result`/`Run`、启动时与运行时能力拆分 |
|
||||
| [voice-input.md](voice-input.zh.md) | 实验性具名语音识别、临时音频和受版本保护的草稿插入 |
|
||||
| [translation.md](translation.zh.md) | 实验性免登录文本翻译与显式 Provider 请求 |
|
||||
| [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、持久 peer mailbox 与共享任务 DAG |
|
||||
| [agent-team.md](agent-team.zh.md) | Agent Teams:隐式 Lead 身份、具名 continuable teammate、直接 peer inbox 投递 与共享任务 DAG |
|
||||
| [claude-code-mods.md](claude-code-mods.zh.md) | 经实验性桥接运行的 Claude Code 模组:与 Claude Code 不同的每个事件、`$` 成员、链规则、绘制特性与示例模组 |
|
||||
| [web.md](web.zh.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` |
|
||||
| [spill.md](spill.zh.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` |
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
en: f571e223220bb616
|
||||
zh: dd7e37fdf359eeae
|
||||
/agent-teams/identity-and-roster:
|
||||
en: c6e6d2add95df5e8
|
||||
zh: 36c0b25c3f4eb80b
|
||||
/agent-teams/durable-mailbox:
|
||||
en: 75fb392a6ccfbdf9
|
||||
zh: f80678b84bd1ea47
|
||||
en: df72d9409b89714d
|
||||
zh: 982934dd18864ca2
|
||||
/agent-teams/messages-and-historical-mailbox:
|
||||
en: 2aed7dd369f47747
|
||||
zh: 4f080e50c584ad37
|
||||
/agent-teams/shared-task-dag:
|
||||
en: 4515005d07e4d2a3
|
||||
zh: 26811b31f69009d6
|
||||
@@ -18,5 +18,5 @@
|
||||
en: 6af99eff8036597a
|
||||
zh: f172f994e01f12c1
|
||||
/agent-teams/replay:
|
||||
en: 4adfd7bad5c0e302
|
||||
zh: 2d71ba065f44f2a1
|
||||
en: fe32f71c6d799ebc
|
||||
zh: aa2b6ccddba8cfd5
|
||||
@@ -6,7 +6,7 @@ Types shared by the experimental implicit-root Team domain, model tools, and hos
|
||||
|
||||
## Identity and roster
|
||||
|
||||
`TeamId` is the root `SessionId` under a distinct [brand](core.md#branded-ids). `TeamTaskId` is Team-local and monotonically allocated as `task-<n>`; `TeamMessageId` is globally random. A teammate's Session id remains its persistent identity, while `name` is an immutable model/UI label.
|
||||
`TeamId` is the root `SessionId` under a distinct [brand](core.md#branded-ids). `TeamTaskId` is allocated as `task-<n>`. New sends return the target inbox `MessageId`; `TeamMessageId` is retained only for historical mailbox records. Teammate names are immutable labels.
|
||||
|
||||
```ts type-equiv
|
||||
/** Whole durable value written on every teammate lifecycle change. */
|
||||
@@ -23,12 +23,12 @@ interface TeamMemberSnapshot {
|
||||
|
||||
Every member starts in `provisioning` and reaches exactly one terminal roster phase, `active` or `failed`. Roster `running`/`inactive` status is derived separately and never rewrites this record.
|
||||
|
||||
## Durable mailbox
|
||||
## Messages and historical mailbox
|
||||
|
||||
The Lead Session first stores the complete queued message. A target receipt is acknowledged only after its pending inbox item or recorded user message is durable, leaving queued-minus-delivered as the recovery mailbox.
|
||||
New sends return inbox acceptance or reject. The target stores the existing `agent-message` source with the real sender Session id and a sender-name content prefix. Team has no new-message outbox or resend deduplication. The [package contract](../../packages/experimental/agent-team/README.md#understand-the-implementation) defines persistence. The following mailbox types remain readable so historical Lead logs still replay; Team does not deliver their pending records.
|
||||
|
||||
```ts type-equiv
|
||||
/** One peer message retained until its target Session records it. */
|
||||
/** One peer message recorded by the historical Team mailbox. */
|
||||
interface TeamMessageSnapshot {
|
||||
readonly id: TeamMessageId
|
||||
readonly senderId: SessionId
|
||||
@@ -40,10 +40,10 @@ interface TeamMessageSnapshot {
|
||||
|
||||
Every message attempts Steer delivery. A running target receives it at the nearest step boundary; an inactive target starts a turn if loaded or cold-resumes otherwise. Scheduling is not stored in the durable record because callers cannot select another mode.
|
||||
|
||||
The target Session keeps message identity and sender attribution on both the pending inbox item and the eventual user message. Folding that source across inbox and history is the target-side de-duplication key; the model-visible framing repeats the id and sender.
|
||||
Historical `team-message` sources retain the old message id and sender attribution; the client uses them for the message title and icon.
|
||||
|
||||
```ts type-equiv
|
||||
/** Source retained by the target Session for durable mailbox de-duplication. */
|
||||
/** Source recorded by a target Session for one historical Team mailbox delivery. */
|
||||
interface TeamMessageSource {
|
||||
readonly kind: 'team-message'
|
||||
readonly teamId: TeamId
|
||||
@@ -122,7 +122,7 @@ interface TeamProjection {
|
||||
|
||||
## Replay
|
||||
|
||||
The `agentTeam` Session projection replays one root Session into the roster, task board, and queued-minus-delivered mailbox that every Team operation reads. It selects records by `TeamId`, so events inherited by an ordinary fork retain the ancestor id and never enter the new root's state. Session event `seq` and `time` remain the ordering and timing record; Team snapshots do not duplicate them. Roster and task reads reach callers as views; pending mail stays internal to delivery and recovery. The package [README](../../packages/experimental/agent-team/README.md) owns operation, authorization, recovery, and limit behavior.
|
||||
The `agentTeam` Session projection replays one root Session into the roster, task board, and historical queued-minus-delivered mailbox. It selects records by `TeamId`, so events inherited by an ordinary fork retain the ancestor id and never enter the new root's state. Session event `seq` and `time` remain the ordering and timing record; Team snapshots do not duplicate them. Roster and task reads reach callers as views; pending mail is not exposed to callers. The package [README](../../packages/experimental/agent-team/README.md) owns operation, authorization, recovery, and limit behavior.
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
@@ -162,10 +162,10 @@ listMembers(agent: Agent): TeamMemberView[]
|
||||
async spawnTeammate(caller: Agent, request: SpawnTeammateRequest): Promise<SpawnTeammateResult>
|
||||
|
||||
/**
|
||||
* Queue one durable peer message, then attempt immediate delivery.
|
||||
* Steer one peer message into the target inbox or reject the attempt.
|
||||
* @param caller - exact live sending Team member.
|
||||
* @param request - target name, content, and pre-queue cancellation.
|
||||
* @returns durable message identity and immediate-delivery observation.
|
||||
* @param request - target name, content, and cancellation before acceptance.
|
||||
* @returns accepted inbox identity; acceptance follows normal Agent persistence and does not await model processing.
|
||||
*/
|
||||
async sendMessage(caller: Agent, request: SendTeamMessageRequest): Promise<SendTeamMessageResult>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 身份与 roster
|
||||
|
||||
`TeamId` 是具有独立[品牌](core.zh.md#branded-ids)的 Root `SessionId`。`TeamTaskId` 在 Team 内按 `task-<n>` 单调分配;`TeamMessageId` 是全局随机值。teammate 的 Session id 始终是持久身份,而 `name` 是不可变的模型/UI 标签。
|
||||
`TeamId` 是使用独立[品牌类型](core.zh.md#branded-ids)的 root `SessionId`。`TeamTaskId` 按 `task-<n>` 分配。新发送返回目标 inbox 的 `MessageId`;`TeamMessageId` 仅供历史 mailbox 记录保留。teammate 名称是不可变标签。
|
||||
|
||||
```ts type-equiv
|
||||
/** Whole durable value written on every teammate lifecycle change. */
|
||||
@@ -23,12 +23,12 @@ interface TeamMemberSnapshot {
|
||||
|
||||
每个 member 都从 `provisioning` 开始,并且只到达一个终态 roster phase:`active` 或 `failed`。roster 的 `running`/`inactive` 状态单独派生,绝不会重写该记录。
|
||||
|
||||
## 持久 mailbox
|
||||
## 消息与历史 mailbox
|
||||
|
||||
Lead Session 首先存储完整 queued message。只有 target 的 pending inbox 条目或已记录用户消息完成持久化,才会写入独立 acknowledgement event,queued-minus-delivered 因而构成恢复 mailbox。
|
||||
新发送返回 inbox 接收结果或拒绝。目标保存现有 `agent-message` source,包含真实发送者 Session id 和发送者名称正文前缀。Team 不为新消息提供 outbox 或重发去重。[包约定](../../packages/experimental/agent-team/README.zh.md#understand-the-implementation)定义持久化。以下 mailbox 类型继续可读,以便历史 Lead 日志仍能回放;Team 不投递其中的 pending 记录。
|
||||
|
||||
```ts type-equiv
|
||||
/** One peer message retained until its target Session records it. */
|
||||
/** One peer message recorded by the historical Team mailbox. */
|
||||
interface TeamMessageSnapshot {
|
||||
readonly id: TeamMessageId
|
||||
readonly senderId: SessionId
|
||||
@@ -40,10 +40,10 @@ interface TeamMessageSnapshot {
|
||||
|
||||
每条消息都会尝试 Steer 投递。running target 在最近的步骤边界收到消息,inactive target 在已加载时启动一个轮次,否则冷恢复。调用方不能选择其他模式,因此持久记录不存储调度方式。
|
||||
|
||||
target Session 会在 pending inbox 条目和最终用户消息上保留消息身份与发送者归因。跨 inbox 与历史折叠该 source 构成 target 侧去重键;模型可见的 framing 会重复 id 和发送者。
|
||||
历史 `team-message` source 保留旧消息 id 与发送者归属;客户端用它们展示消息标题和图标。
|
||||
|
||||
```ts type-equiv
|
||||
/** Source retained by the target Session for durable mailbox de-duplication. */
|
||||
/** Source recorded by a target Session for one historical Team mailbox delivery. */
|
||||
interface TeamMessageSource {
|
||||
readonly kind: 'team-message'
|
||||
readonly teamId: TeamId
|
||||
@@ -122,7 +122,7 @@ interface TeamProjection {
|
||||
|
||||
## 回放
|
||||
|
||||
`agentTeam` Session 投影把一个 Root Session 回放成每个 Team 操作所读取的 roster、任务板与 queued-minus-delivered mailbox。它按 `TeamId` 选取记录,因此普通 fork 继承的 event 保留 ancestor id,绝不会进入新 Root 的状态。Session event 的 `seq` 与 `time` 继续负责顺序和时间记录,Team snapshot 不再重复保存它们。roster 与 task 读取以 view 形式到达调用方,而 pending 邮件仅供投递与恢复内部使用。包 [README](../../packages/experimental/agent-team/README.zh.md)负责 operation、authorization、recovery 和限制行为。
|
||||
`agentTeam` Session 投影把一个 Root Session 回放成roster、任务板与历史 queued-minus-delivered mailbox。它按 `TeamId` 选取记录,因此普通 fork 继承的 event 保留 ancestor id,绝不会进入新 Root 的状态。Session event 的 `seq` 与 `time` 继续负责顺序和时间记录,Team snapshot 不再重复保存它们。roster 与 task 读取以 view 形式到达调用方,而 pending 邮件不对调用方公开。包 [README](../../packages/experimental/agent-team/README.zh.md)负责 operation、authorization、recovery 和限制行为。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
@@ -162,10 +162,10 @@ listMembers(agent: Agent): TeamMemberView[]
|
||||
async spawnTeammate(caller: Agent, request: SpawnTeammateRequest): Promise<SpawnTeammateResult>
|
||||
|
||||
/**
|
||||
* Queue one durable peer message, then attempt immediate delivery.
|
||||
* Steer one peer message into the target inbox or reject the attempt.
|
||||
* @param caller - exact live sending Team member.
|
||||
* @param request - target name, content, and pre-queue cancellation.
|
||||
* @returns durable message identity and immediate-delivery observation.
|
||||
* @param request - target name, content, and cancellation before acceptance.
|
||||
* @returns accepted inbox identity; acceptance follows normal Agent persistence and does not await model processing.
|
||||
*/
|
||||
async sendMessage(caller: Agent, request: SendTeamMessageRequest): Promise<SendTeamMessageResult>
|
||||
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
en: 962b4398c9052959
|
||||
zh: 14471c340a9f6e60
|
||||
/profile-management/management-records:
|
||||
en: a10d3f358e9b7c8e
|
||||
zh: 28b0f44589a5020d
|
||||
en: a1412da0219c2a09
|
||||
zh: b6869fd548f4c693
|
||||
@@ -6,8 +6,6 @@ The [boot package group](../../packages/boot/README.md) owns launcher-provided p
|
||||
|
||||
## Management records
|
||||
|
||||
`ProfilePatch` adds an optional literal `preset` row id to native Include patch fields; `id` then targets a child in that preset. [App-boot](../../packages/boot/app-boot/README.md#profiles) owns compilation, ordering, validation, and path resolution.
|
||||
|
||||
`PluginEntryId` identifies one Loader entry; callers obtain it from `listPlugins` rather than constructing a patch id.
|
||||
|
||||
`PluginInfo` carries module identity, effective enablement, fiber phase and optional display `meta`, plus a unique `patchId` or a `readOnlyReason`.
|
||||
@@ -32,7 +30,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
|
||||
|
||||
### `ctx.configEditor` — `ConfigEditor`
|
||||
|
||||
Persist Host entry configs without changing preset-scoped operations, then reconcile through Loader.
|
||||
Persist complete raw configs and apply them through the normal Loader path.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Addressable profile rows; nested Includes have independent configuration ownership.
|
||||
@@ -116,7 +114,7 @@ Manage profile files and apply their declared reload lifecycle.
|
||||
* @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional
|
||||
* display metadata, activation selections, whether the installation offers the bundle, and removal availability.
|
||||
*/
|
||||
@Remote async listBundles(): Promise<BundleInfo[]>
|
||||
@Remote listBundles(): Promise<BundleInfo[]>
|
||||
|
||||
/** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names.
|
||||
* @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now.
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
## 管理记录
|
||||
|
||||
`ProfilePatch` 在原生 Include patch 字段上增加可选的字面量 `preset` 条目 id;此时 `id` 指向该 preset 内的子条目。[App-boot](../../packages/boot/app-boot/README.zh.md#profiles) 定义编译、顺序、校验与路径解析规则。
|
||||
|
||||
`PluginEntryId` 标识一个 Loader 条目;调用方从 `listPlugins` 获取,不自行拼接 patch id。
|
||||
|
||||
`PluginInfo` 包含模块标识、实际启停状态、fiber 阶段和可选的展示 `meta`,以及唯一的 `patchId` 或 `readOnlyReason`。
|
||||
@@ -32,7 +30,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
|
||||
|
||||
### `ctx.configEditor` — `ConfigEditor`
|
||||
|
||||
Persist Host entry configs without changing preset-scoped operations, then reconcile through Loader.
|
||||
Persist complete raw configs and apply them through the normal Loader path.
|
||||
|
||||
```ts cordis-catalog
|
||||
/** Addressable profile rows; nested Includes have independent configuration ownership.
|
||||
@@ -116,7 +114,7 @@ Manage profile files and apply their declared reload lifecycle.
|
||||
* @returns Package versions, manifest descriptions, the installable spec of profile dependencies, rows, optional
|
||||
* display metadata, activation selections, whether the installation offers the bundle, and removal availability.
|
||||
*/
|
||||
@Remote async listBundles(): Promise<BundleInfo[]>
|
||||
@Remote listBundles(): Promise<BundleInfo[]>
|
||||
|
||||
/** Read the registries this manager asks: the configured first one, its fallbacks in order, and what pnpm's own configuration names.
|
||||
* @returns The registries in pnpm's comparison form; null is the one pnpm's own configuration names, `resolved` as pnpm reads it now.
|
||||
|
||||
@@ -497,12 +497,12 @@ async register(definition: PresetDefinition): Promise<() => Promise<void>>
|
||||
|
||||
/** Inspect retained revisions, or the exact revision an Agent joined.
|
||||
* @param ctx - optional Agent context; omission includes all retained revisions.
|
||||
* @returns detached definition and module row identities, resolution bases, and isolation diagnostics; no match returns an empty list.
|
||||
* @returns detached module references and isolation diagnostics; no match returns an empty list.
|
||||
*/
|
||||
inspectCompositions(ctx?: Context): AgentPresetInspection[]
|
||||
|
||||
/** Read every declared preset, including activation failures.
|
||||
* @returns Display metadata, declaring Loader row identities, and loading diagnostics.
|
||||
* @returns Display metadata and loading diagnostics.
|
||||
*/
|
||||
async list(): Promise<AgentPreset[]>
|
||||
|
||||
|
||||
@@ -507,12 +507,12 @@ async register(definition: PresetDefinition): Promise<() => Promise<void>>
|
||||
|
||||
/** Inspect retained revisions, or the exact revision an Agent joined.
|
||||
* @param ctx - optional Agent context; omission includes all retained revisions.
|
||||
* @returns detached definition and module row identities, resolution bases, and isolation diagnostics; no match returns an empty list.
|
||||
* @returns detached module references and isolation diagnostics; no match returns an empty list.
|
||||
*/
|
||||
inspectCompositions(ctx?: Context): AgentPresetInspection[]
|
||||
|
||||
/** Read every declared preset, including activation failures.
|
||||
* @returns Display metadata, declaring Loader row identities, and loading diagnostics.
|
||||
* @returns Display metadata and loading diagnostics.
|
||||
*/
|
||||
async list(): Promise<AgentPreset[]>
|
||||
|
||||
|
||||
@@ -193,7 +193,8 @@ async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnaps
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* active work; a replacement may register once disposal has started, and the
|
||||
* closing provider's late results never commit.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
|
||||
@@ -193,7 +193,8 @@ async refresh(session: Session, signal?: AbortSignal): Promise<SessionTitleSnaps
|
||||
|
||||
/**
|
||||
* Register the sole optional title provider. Disposal aborts its pending and
|
||||
* active work before another provider may register.
|
||||
* active work; a replacement may register once disposal has started, and the
|
||||
* closing provider's late results never commit.
|
||||
* @param provider - provider identity, cadence, and generation function.
|
||||
* @returns exact Cordis effect disposer, which settles after active calls quiesce.
|
||||
*/
|
||||
|
||||
@@ -184,7 +184,7 @@ interface ContinuableCreateSpec {
|
||||
sendMessage authorizes the exact live sender and permits only adjacent local agents. Running children receive steering input; absent continuable children resume from persistence. External executions reject follow-ups. Both local and external executions automatically report their final text, structured result, diagnostic, and status in the parent settlement notice. Local children can also send messages through send_message. caller delivery suppresses settlement notices. drainDescendants and drainChildren await owned work teardown for local and external activations.
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable attribution for one model-authored message between adjacent Agents. */
|
||||
/** Durable attribution for one model-authored message between Agents. */
|
||||
interface AgentMessageSource {
|
||||
readonly kind: 'agent-message'
|
||||
/** A message another agent addressed to this one (`relay` context form). */
|
||||
|
||||
@@ -184,7 +184,7 @@ interface ContinuableCreateSpec {
|
||||
sendMessage 根据确切的在线发送者实例授权,仅允许相邻本地代理通信。运行中的子代理接收引导输入;不在线的可继续子代理从持久化恢复。外部执行拒绝后续消息。本地与外部执行都会通过父级结束通知自动回传最终文本、结构化结果、诊断和状态。本地子代理也可以通过 send_message 发送消息。caller 回传会抑制结束通知。drainDescendants 与 drainChildren 对本地和外部 activation 都等待所属任务清理完成。
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable attribution for one model-authored message between adjacent Agents. */
|
||||
/** Durable attribution for one model-authored message between Agents. */
|
||||
interface AgentMessageSource {
|
||||
readonly kind: 'agent-message'
|
||||
/** A message another agent addressed to this one (`relay` context form). */
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 6cc2cd9ced70bb09
|
||||
zh: 1c0e6cb4ba2ed637
|
||||
/tool-schema-catalog/tool-package-map:
|
||||
en: 7caa258c67464109
|
||||
zh: 53bfb5581c48dc45
|
||||
en: e18a3e3c916afb23
|
||||
zh: 4284b50a5e9bd12f
|
||||
/tool-schema-catalog/deepseek-ai-dsh-plugin-manager:
|
||||
en: e0143916bb634762
|
||||
zh: e0143916bb634762
|
||||
@@ -264,8 +264,8 @@
|
||||
en: f9457f033f75f00a
|
||||
zh: a2d60ec44118c34c
|
||||
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/send-message:
|
||||
en: e99455aec4656ff2
|
||||
zh: a8f88ec35cfe38b8
|
||||
en: ec50e6a0b86aecc6
|
||||
zh: 29c8728536a5adf1
|
||||
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/spawn-teammate:
|
||||
en: 02d6cc746e234b99
|
||||
zh: 49bc60ac098bca48
|
||||
@@ -282,8 +282,8 @@
|
||||
en: 2eac7360ba68b1bd
|
||||
zh: b5d676bf288c245e
|
||||
/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/wait-agent:
|
||||
en: fbc4d75a9bcf6816
|
||||
zh: 672131ac2705f411
|
||||
en: e70c77a45832bb28
|
||||
zh: 7d03bec294b65452
|
||||
/tool-schema-catalog/deepseek-ai-dsh-tool-todo:
|
||||
en: dea62d26fb18ff68
|
||||
zh: dea62d26fb18ff68
|
||||
|
||||
@@ -40,7 +40,7 @@ This table connects model-visible tool names to the plugin package and service s
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`, `subagent` | `ctx.tools`, `ctx.subagents`, `ctx.systemPrompt`, `ctx.llm for model discovery and selected-route validation` | `tool/call`, `tool/result`, `child session events through the chosen provider` | `subagent`, `subagent_fork` | The registered delegation name is the load-time `toolName` config (default `subagent`); the default schema above has model selection off, while the discovery schema is shown as the fixed companion available in an enabled Session. Web presets sample the Plugins preference for each new top-level Session and preserve that decision for its child Sessions; `subagent_fork` remains fixed-route. Each instance selects its backend through `provider` and reads model-selection settings through `modelSelectionSettings`. Every delegation returns a child id promptly; local children accept follow-ups, while external backends report their single execution automatically. |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`, `list_agents`, `send_message` | `ctx.tools`, `ctx.subagents`, `ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`, `tool/result`, `child session events through ctx.subagents` | - | The globally named discovery, interruption, and local-message tools over managed subagents: provider-bound `tool-subagent` instances register distinct delegation tools, while this package registers `send_message` and `interrupt_agent` once, plus `list_agents` from its separately loaded `/list-agents` plugin (whose catalog rows use the sessionProjections and live Agent registries). |
|
||||
| `@deepseek-ai/dsh-tool-jobs` | `job_kill`, `job_list`, `job_output` | `ctx.tools`, `ctx.jobs`, `ctx.systemPrompt` | `tool/call`, `tool/result`, `user/message via agent.inject() for background completion notices` | - | The kind-agnostic background-job controller: background bash commands and PTY sends are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. |
|
||||
| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/message/queued`, `team/message/delivered`, `team/task`, `tool/result` | - | All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. |
|
||||
| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`, `list_agents`, `send_message`, `spawn_teammate`, `team_task_create`, `team_task_get`, `team_task_list`, `team_task_update`, `wait_agent` | `ctx.tools`, `ctx.systemPrompt`, `ctx.agentTeams`, `an exact live Team member Agent` | `tool/call`, `team/member`, `team/task`, `agent/inbox/spliced`, `user/message on inbox claim`, `tool/result` | - | All nine tools are scoped to implicit Team Leads and durable teammates. The shipped dsh-base bundle keeps the package disabled; the documented Agent Teams profile patch enables it while disabling the legacy continuable-child control names. |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`, `owning Agent session` | `tool/call`, `todo/write`, `tool/result` | - | todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task. |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | Reads or changes the calling Session directory. Existing shells and processes retain their own directories; origin metadata and permission roots stay fixed. |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | Explicit experimental composition only. Creates a new branch and checkout from a pinned local commit under existing write permissions, then changes the Session directory. Leaving retains the branch and checkout. |
|
||||
@@ -2257,7 +2257,7 @@ Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/exper
|
||||
|
||||
### `send_message`
|
||||
|
||||
Send one durable message to another Team member. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.
|
||||
Send one message to another Team member. A running target receives it at the nearest step boundary; an inactive target starts or resumes a turn.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -2489,7 +2489,7 @@ Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/exper
|
||||
|
||||
### `wait_agent`
|
||||
|
||||
Wait for the next teammate status, mailbox, or shared-task change after this call starts. This never wakes inactive members and returns noProgress immediately when no other member is running or provisioning. Re-list after wakeup or timeout instead of polling.
|
||||
Wait for the next teammate status, message, or shared-task change after this call starts. This never wakes inactive members and returns noProgress immediately when no other member is running or provisioning. Re-list after wakeup or timeout instead of polling.
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
| `@deepseek-ai/dsh-tool-subagent` | `list_subagent_models`、`subagent` | `ctx.tools`、`ctx.subagents`、`ctx.systemPrompt`、`用于模型发现和所选路由校验的 ctx.llm` | `tool/call`、`tool/result`、`child session events through the chosen provider` | `subagent`、`subagent_fork` | 注册的委派工具名称取决于加载时 `toolName` 配置(默认为 `subagent`);上述默认 schema 关闭模型选择,而发现 schema 则展示为已启用 Session 中可用的固定配套工具。Web preset 会在每个新顶层 Session 创建时读取插件页偏好,并为其子 Session 保留该决定;`subagent_fork` 始终使用固定路由。每个实例通过 `provider` 选择后端,通过 `modelSelectionSettings` 读取模型选择设置。每次委派都会立即返回子代理 id;本地子代理接受后续消息,外部后端自动汇报单次执行结果。 |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `interrupt_agent`、`list_agents`、`send_message` | `ctx.tools`、`ctx.subagents`、`ctx.agents and ctx.sessionProjections (list_agents only)` | `tool/call`、`tool/result`、`child session events through ctx.subagents` | - | 这些是用于发现、中断托管 subagent 和向本地子代理发送消息的全局命名工具:绑定提供方的 `tool-subagent` 实例注册不同的委派工具;本包注册一次 `send_message` 和 `interrupt_agent`,另由 `list_agents` 通过单独加载的 `/list-agents` 插件提供,其目录行使用 sessionProjections 和实时 Agent 注册表。 |
|
||||
| `@deepseek-ai/dsh-tool-jobs` | `job_kill`、`job_list`、`job_output` | `ctx.tools`、`ctx.jobs`、`ctx.systemPrompt` | `tool/call`、`tool/result`、`user/message via agent.inject() for background completion notices` | - | 与任务种类无关的后台任务控制器:后台 bash 命令与 PTY 发送 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 |
|
||||
| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/message/queued`、`team/message/delivered`、`team/task`、`tool/result` | - | 这 9 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 |
|
||||
| `@deepseek-ai/dsh-experimental-tool-agent-team` | `interrupt_agent`、`list_agents`、`send_message`、`spawn_teammate`、`team_task_create`、`team_task_get`、`team_task_list`、`team_task_update`、`wait_agent` | `ctx.tools`、`ctx.systemPrompt`、`ctx.agentTeams`、`an exact live Team member Agent` | `tool/call`、`team/member`、`team/task`、`agent/inbox/spliced`、`user/message on inbox claim`、`tool/result` | - | 这 9 个工具限定于隐式 Team Lead 与持久 teammate 作用域。随产品发布的 dsh-base bundle 默认禁用该包;文档中的 Agent Teams profile patch 会启用它,并禁用旧 continuable child 的同名控制工具。 |
|
||||
| `@deepseek-ai/dsh-tool-todo` | `todo_write` | `ctx.tools`、`owning Agent session` | `tool/call`、`todo/write`、`tool/result` | - | todo_write 是会话所有的状态;UI 将最新的 todo/write 事件渲染为检查清单。`allowParallelInProgress` 是没有默认值的必填项,因此本目录明确选择 `true`,对应描述允许同时存在多个 `in_progress` 项。选择 `false` 的部署会获得同一工具,但描述会要求只能有 1 个活动任务。 |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `working_directory` | `ctx.tools`, `ctx.workingDirectory` | `tool/call`, `working-directory/change`, `user context for directory changes`, `tool/result` | - | 读取或改变调用方 Session 的目录。已有 shell 和进程保留自己的目录;原始元数据和权限根目录保持不变。 |
|
||||
| `@deepseek-ai/dsh-experimental-tool-worktree` | `create_worktree` | `ctx.tools`, `ctx.worktrees`, `a calling Agent` | `tool/call`, `Git branch and checkout`, `working-directory/change`, `tool/result` | - | 仅通过显式实验组合加载。在现有写权限下从固定的本地提交创建新分支与检出目录,再改变 Session 工作目录。离开时保留分支与检出目录。 |
|
||||
@@ -2263,7 +2263,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
|
||||
|
||||
### `send_message`
|
||||
|
||||
向另一名 Team member 发送一条持久消息。running target 会在最近的步骤边界收到消息;inactive target 会启动或恢复一个 turn。
|
||||
向另一名 Team member 发送一条消息。running target 会在最近的步骤边界收到消息;inactive target 会启动或恢复一个 turn。
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -2495,7 +2495,7 @@ lsp 工具将提供方选择和语言服务器子进程置于 ctx.lsp 之后,
|
||||
|
||||
### `wait_agent`
|
||||
|
||||
等待本次调用开始后下一次 teammate 状态、mailbox 或共享任务变更。它绝不会唤醒 inactive member;若没有其他 member 正在 running 或 provisioning,则立即返回 noProgress。唤醒或超时后应重新列出状态,而不是轮询。
|
||||
等待本次调用开始后下一次 teammate 状态、消息或共享任务变更。它绝不会唤醒 inactive member;若没有其他 member 正在 running 或 provisioning,则立即返回 noProgress。唤醒或超时后应重新列出状态,而不是轮询。
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/desktop-test-auth-popup/guide.md
|
||||
/:
|
||||
en: 97a3763818ba118c
|
||||
zh: ba856458827cb637
|
||||
/enable-test-update-policy-authentication-dialogs-locally:
|
||||
en: 33515a78d8b30d7a
|
||||
zh: 6f821057b75e319e
|
||||
/enable-test-update-policy-authentication-dialogs-locally/change:
|
||||
en: 21d23ef8d1d3599c
|
||||
zh: 389633b096767f73
|
||||
/enable-test-update-policy-authentication-dialogs-locally/migration:
|
||||
en: e6b3bc48daaa13b1
|
||||
zh: df50a981844d3de6
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Desktop test builds require local opt-in before showing any Feishu update-policy authentication dialogs."
|
||||
---
|
||||
|
||||
# Enable test update-policy authentication dialogs locally
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Test builds previously offered Feishu login after an unauthenticated startup or manual update-policy check. All test authentication dialogs now default to disabled, including manual checks and mandatory-update refreshes. Test gateway cookies do not survive restart. With login disabled, a gateway requiring authentication cannot provide new mandatory-update decisions; enable the setting, restart and sign in to obtain them. Authentication failures retain any mandatory block already known in the same process; production policy requests and product account login are unchanged.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Launch the updated application once to create `app.getPath('userData')/desktop/settings.json` if missing. Existing files are retained, and a missing field defaults to `false`. See [local desktop settings](../../../../apps/desktop/README.md#local-desktop-settings) for platform paths and error recovery.
|
||||
2. To permit Feishu login, set `updates.allowTestAuthPopupWindow` to `true` in that file, then fully quit and restart Desktop. An unauthenticated test-policy response can now offer login. Leave the default unchanged if no login dialogs are wanted.
|
||||
3. To disable dialogs again, set the field to `false` and restart. Startup and manual checks must not show Feishu authentication dialogs. Updates still query policy, and authentication failures do not clear a known mandatory block.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "桌面测试包需要在本地显式开启后,才能显示飞书更新策略鉴权弹窗。"
|
||||
---
|
||||
|
||||
# 在本地开启测试更新策略鉴权弹窗
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
测试包此前会在启动或手动检查更新策略未通过鉴权后提供飞书登录入口。现在所有测试鉴权弹窗默认关闭,包括手动检查和强更刷新。测试网关 Cookie 不跨重启保存。禁止登录时,需要鉴权的网关无法提供新的强更决策;需开启设置、重启并登录后才能获取。鉴权失败仍保留同一进程中已知的强更阻塞;正式环境策略请求和产品账号登录保持不变。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 启动更新后的应用一次,在 `app.getPath('userData')/desktop/settings.json` 缺失时生成该文件。已有文件会保留,字段缺失时默认为 `false`。平台路径和错误恢复方法见[本地桌面设置](../../../../apps/desktop/README.zh.md#local-desktop-settings)。
|
||||
2. 如需允许飞书登录,在文件中将 `updates.allowTestAuthPopupWindow` 设为 `true`,然后完全退出并重启 Desktop。此时测试策略的未鉴权响应可以提供登录入口。不需要登录弹窗时保持默认值即可。
|
||||
3. 如需再次关闭弹窗,将字段设为 `false` 并重启。启动和手动检查均不得显示飞书鉴权弹窗。更新仍会查询策略,鉴权失败不会解除已知强更阻塞。
|
||||
@@ -3,14 +3,14 @@
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md
|
||||
/:
|
||||
en: a77928d6130fe601
|
||||
zh: da46d612268c3ced
|
||||
/native-subagent-bundles-include-preset-tools:
|
||||
en: b5c79b41fc34574a
|
||||
zh: d784adcf343b0817
|
||||
/native-subagent-bundles-include-preset-tools/change:
|
||||
en: 7d4af3e4ab91ef72
|
||||
zh: 837377c96d5eee12
|
||||
/native-subagent-bundles-include-preset-tools/migration:
|
||||
en: 3257a5d18d07cada
|
||||
zh: b663a7ae020bbfee
|
||||
en: 4e1c7b1bfb2ba5ca
|
||||
zh: c3f5ac60d10a215f
|
||||
/native-subagent-bundles-include-global-tools:
|
||||
en: 8f785a4b502f1f78
|
||||
zh: 527952686410adba
|
||||
/native-subagent-bundles-include-global-tools/change:
|
||||
en: 3432d6ba2ccf6492
|
||||
zh: 41cdf90f9d5b330d
|
||||
/native-subagent-bundles-include-global-tools/migration:
|
||||
en: 304dc5243b2f8159
|
||||
zh: 38cd9aa136dc8009
|
||||
@@ -1,32 +1,31 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Claude Code and Codex bundles add delegation tools to full Web presets while retaining their package and row identities."
|
||||
description: "Claude Code and Codex bundles add global delegation tools while retaining their package and row identities."
|
||||
---
|
||||
|
||||
# Native subagent bundles include preset tools
|
||||
# Native subagent bundles include global tools
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`@deepseek-ai/dsh-subagent-claude-code` and `@deepseek-ai/dsh-subagent-codex` keep their package names, bundle selections, provider row IDs, configuration, and native authentication. In Web and Desktop, a selected bundle now adds enabled delegation tools to `preset-standard`, `preset-cordis`, and `preset-ptc`; previously the bundle registered only its provider. Existing Agents retain their composition; new Agents and subsequently reopened Sessions receive the selected tools.
|
||||
`@deepseek-ai/dsh-subagent-claude-code` and `@deepseek-ai/dsh-subagent-codex` keep their package names, bundle selections, provider row IDs, configuration, and native authentication. A selected bundle now also registers its delegation tool as a Host global tool, visible to every preset including minimal; previously the bundle registered only its provider. Live Agents see the tool from their next request after the bundle is enabled, and lose it after the bundle is switched off.
|
||||
|
||||
The contributed rows retain `tool-subagent-claude-code` and `tool-subagent-codex`. Later user layers can override their configuration or disabled state, and a complete preset configuration replacement keeps its own child list. An override does not select an unselected bundle. Duplicate declarations of the same model-facing tool still fail.
|
||||
The contributed Host rows are `tool-subagent-claude-code` and `tool-subagent-codex`. Later user layers can override their configuration or disabled state by row id. An override does not select an unselected bundle. A preset that registers its own tool with the same name shadows the global tool for its Agents.
|
||||
|
||||
Delegation follows the shared managed-activation API: the tool returns a child ID, and completion arrives as a logged notice to the parent Agent. Remove obsolete `backgroundMode` settings; `maxDepth: provider-managed` remains supported for these native providers.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Preserve selected bundle names, provider configuration, and authentication. Existing provider-only packages need **Update** in Plugins → Official to receive the tool-contributing bundle; restart when requested. To adopt a new provider, enable its card. [Installation targets](../../../../packages/boot/plugin-manager/README.md#use-this-package) follow the published DSH version or source checkout; equal version strings do not make an older registry package equivalent to a development link. Off only deselects; Remove remains separate.
|
||||
2. Configure the familiar tool row ID with a scoped patch. Configuration overrides still replace the complete configuration. For example, this keeps Codex disabled in standard while other full presets use their selected composition:
|
||||
2. Configure the tool with an ordinary row patch. Configuration overrides still replace the complete configuration. For example, this keeps the Codex tool disabled while the provider stays registered:
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
id: tool-subagent-codex
|
||||
- id: tool-subagent-codex
|
||||
disabled: true
|
||||
```
|
||||
|
||||
3. If an additional manual row registers the same tool in a preset, remove that redundant row or disable the bundle's contribution explicitly. A copied complete preset can retain its existing tool declarations and disabled state. Verify the effective tool catalog in a new Session; minimal keeps its current shell and working-directory tools.
|
||||
3. If a manual Host row registers the same tool name, remove that redundant row or disable the bundle's row. Verify the effective tool catalog in a Session.
|
||||
4. Plugin clients receive required `BundleInfo.official` and `BundleInfo.availability` fields; detail-slot `PluginPackageRef` receives them too. `official` identifies project maintenance, `availability` reports `installation`, `profile`, or `missing`, and `installed` still means a profile dependency declaration. Detail-slot readers that only display existing fields need no changes; constructors and exact validators must include the new fields. Optional `installTarget` identifies the offered version and registry or local-link spec.
|
||||
|
||||
**Other profiles.** Headless, SDK, ACP, and custom profiles without the three full preset targets are outside this compatibility scope. Selecting these bundle layers fails for missing preset targets and can retain the saved selection; there is no automatic rollback. Such profiles can keep the installed provider package without selecting its bundle and explicitly compose provider/tool rows using the [Codex](../../../../packages/subagent/subagent-codex/README.md#exposing-the-tool) or [Claude Code](../../../../packages/subagent/subagent-claude-code/README.md#exposing-the-tool) example.
|
||||
**Other profiles.** The bundle layers insert Host rows only, so headless, SDK, ACP, and custom profiles can select them as well. Profiles that keep the provider package without selecting its bundle can compose the same rows using the [Codex](../../../../packages/subagent/subagent-codex/README.md#exposing-the-tool) or [Claude Code](../../../../packages/subagent/subagent-claude-code/README.md#exposing-the-tool) example.
|
||||
@@ -1,32 +1,31 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Claude Code 与 Codex bundle 为完整 Web 预设添加委派工具,同时保留包名和行标识。"
|
||||
description: "Claude Code 与 Codex bundle 添加全局委派工具,同时保留包名和行标识。"
|
||||
---
|
||||
|
||||
# 原生子代理 bundle 包含预设工具
|
||||
# 原生子代理 bundle 包含全局工具
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
`@deepseek-ai/dsh-subagent-claude-code` 和 `@deepseek-ai/dsh-subagent-codex` 保留包名、bundle 选择、提供方行 ID、配置及原生认证。在 Web 和 Desktop 中,已选中的 bundle 现在会为 `preset-standard`、`preset-cordis` 和 `preset-ptc` 添加启用的委派工具;此前 bundle 只注册提供方。已有 Agent 保留其组合;新 Agent 和之后重新打开的 Session 使用选中的工具。
|
||||
`@deepseek-ai/dsh-subagent-claude-code` 和 `@deepseek-ai/dsh-subagent-codex` 保留包名、bundle 选择、提供方行 ID、配置及原生认证。已选中的 bundle 现在还会把委派工具注册为 Host 全局工具,包括 minimal 在内的所有预设都可见;此前 bundle 只注册提供方。启用 bundle 后,运行中的 Agent 从下一次请求起即可看到该工具;关闭后则不再可见。
|
||||
|
||||
贡献行保留 `tool-subagent-claude-code` 和 `tool-subagent-codex`。后续用户层可以覆盖其配置或禁用状态,完整的预设配置替换保留自己的子列表。覆盖不会选中未选中的 bundle。重复声明同一个面向模型的工具仍会失败。
|
||||
贡献的 Host 行为 `tool-subagent-claude-code` 和 `tool-subagent-codex`。后续用户层可以按行 ID 覆盖其配置或禁用状态。覆盖不会选中未选中的 bundle。预设自行注册的同名工具会为其 Agent 遮蔽该全局工具。
|
||||
|
||||
委派遵循共享的托管 activation API:工具返回子级 ID,完成结果通过已记录的通知发送给父 Agent。移除已废弃的 `backgroundMode` 设置;这些原生提供方仍支持 `maxDepth: provider-managed`。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 保留已选中的 bundle 名称、提供方配置和认证。已有仅提供方的包需要在插件 → 官方中**更新**才能获得贡献工具的 bundle;按提示重启。要使用新的提供方,启用其卡片。[安装目标](../../../../packages/boot/plugin-manager/README.zh.md#use-this-package)对应已发布的 DSH 版本或源码 checkout;版本字符串相同不代表旧注册表包等同于开发链接。关闭只取消选择;删除仍为独立操作。
|
||||
2. 使用作用域补丁配置熟悉的工具行 ID。配置覆盖仍会替换整份配置。例如,以下补丁在 standard 中保持 Codex 禁用,其他完整预设则使用其选中的组合:
|
||||
2. 使用普通行补丁配置该工具。配置覆盖仍会替换整份配置。例如,以下补丁在保留提供方注册的同时禁用 Codex 工具:
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
id: tool-subagent-codex
|
||||
- id: tool-subagent-codex
|
||||
disabled: true
|
||||
```
|
||||
|
||||
3. 如果额外的手动行在预设中注册了同一个工具,移除该重复行,或显式禁用 bundle 的贡献行。复制的完整预设可以保留其现有工具声明和禁用状态。在新 Session 中验证有效工具目录;minimal 保留当前的 shell 和工作目录工具。
|
||||
3. 如果手动的 Host 行注册了同名工具,移除该重复行,或禁用 bundle 的贡献行。在 Session 中验证有效工具目录。
|
||||
4. 插件客户端收到必填的 `BundleInfo.official` 和 `BundleInfo.availability` 字段;详情 slot 的 `PluginPackageRef` 也会收到它们。`official` 标识项目维护,`availability` 表示 `installation`、`profile` 或 `missing`,`installed` 仍表示 profile 的依赖声明。只展示已有字段的详情 slot 读取方无需修改;构造方和精确校验器必须包含新增字段。可选的 `installTarget` 标识所提供的版本及注册表或本地链接 spec。
|
||||
|
||||
**其他 profile。** Headless、SDK、ACP 和缺少三个完整预设目标的自定义 profile 不在此次兼容范围内。选择这些 bundle 层会因缺少预设目标而失败,并可能保留已保存的选择;没有自动回滚。这些 profile 可以保留已安装的提供方包而不选择其 bundle,并参考 [Codex](../../../../packages/subagent/subagent-codex/README.zh.md#exposing-the-tool) 或 [Claude Code](../../../../packages/subagent/subagent-claude-code/README.zh.md#exposing-the-tool) 示例显式组合提供方和工具行。
|
||||
**其他 profile。** 这些 bundle 层只插入 Host 行,因此 headless、SDK、ACP 和自定义 profile 也可以选择它们。保留提供方包而不选择其 bundle 的 profile,可以参考 [Codex](../../../../packages/subagent/subagent-codex/README.zh.md#exposing-the-tool) 或 [Claude Code](../../../../packages/subagent/subagent-claude-code/README.zh.md#exposing-the-tool) 示例组合相同的行。
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 5e4b26181326018f
|
||||
zh: 99bc75dcd12bf647
|
||||
/start-a-fresh-agent-with-new-session/change:
|
||||
en: ab12aadb31d0d3e8
|
||||
zh: 637b446aef86456e
|
||||
en: 45a6960304768d90
|
||||
zh: 4b105688ffdd36e1
|
||||
/start-a-fresh-agent-with-new-session/migration:
|
||||
en: 9565e66b6bcdf269
|
||||
zh: 226e470de85aa8b6
|
||||
en: 4642b2a1aee12fb3
|
||||
zh: 53ae60e9d612ab6b
|
||||
@@ -9,12 +9,12 @@ English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
In Web and Desktop, New Session creates a fresh Session and Agent using the current preset definition. It previously reused an existing empty Session in the workspace, which could retain tools from before an optional bundle changed.
|
||||
In Web and Desktop, New Session creates a fresh Session and Agent using the current preset definition. It previously reused an existing empty Session in the workspace, which could retain an earlier preset definition.
|
||||
|
||||
Client calls to `uiWorkspace.startSession(workspaceId)` follow the same behavior. Workspace reconnection through `openWorkspace` or `connectWorkspace` still reuses an eligible blank. Calls with draft-preparation options preserve their existing reuse and content rules. Existing Agents retain their composition.
|
||||
Client calls to `uiWorkspace.startSession(workspaceId)` follow the same behavior. Workspace reconnection through `openWorkspace` or `connectWorkspace` still reuses an eligible blank. Calls with draft-preparation options preserve their existing reuse and content rules. Existing Agents retain their preset composition; global optional tools update on their next request when a bundle is switched.
|
||||
|
||||
## Migration
|
||||
|
||||
1. After changing optional bundles, use New Session to obtain the current tool composition. Continue in an existing Session when its retained composition is intentional.
|
||||
1. After changing a preset definition, use New Session to create an Agent with the current definition. Continue in an existing Session when its retained preset composition is intentional.
|
||||
2. Client integrations that reconnect a workspace should use `openWorkspace` or `connectWorkspace`. Keep explicit draft-preparation options when preparing an existing draft.
|
||||
3. Confirm that the new Agent has the selected tools; no persisted Session files or profile patches need rewriting.
|
||||
3. Confirm that the new Agent uses the updated preset definition; no persisted Session files or profile patches need rewriting.
|
||||
@@ -9,12 +9,12 @@ description: "新会话会创建新的 Agent,不再复用已有空 Session。"
|
||||
|
||||
## 变更
|
||||
|
||||
在 Web 和 Desktop 中,新会话按当前预设定义创建新的 Session 和 Agent。此前该操作会复用工作区内已有的空 Session,因而可能保留可选组合包变更前的工具。
|
||||
在 Web 和 Desktop 中,新会话按当前预设定义创建新的 Session 和 Agent。此前该操作会复用工作区内已有的空 Session,因而可能保留较早的预设定义。
|
||||
|
||||
客户端调用 `uiWorkspace.startSession(workspaceId)` 采用相同行为。通过 `openWorkspace` 或 `connectWorkspace` 重连工作区时仍复用符合条件的空 Session。带草稿准备选项的调用保留已有复用与内容规则。已有 Agent 保留其组合。
|
||||
客户端调用 `uiWorkspace.startSession(workspaceId)` 采用相同行为。通过 `openWorkspace` 或 `connectWorkspace` 重连工作区时仍复用符合条件的空 Session。带草稿准备选项的调用保留已有复用与内容规则。已有 Agent 保留其预设组合;切换组合包后,全局可选工具会在其下一次请求时更新。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 更改可选组合包后,通过新会话取得当前工具组合。需要保留原组合时,继续使用已有 Session。
|
||||
1. 更改预设定义后,通过新会话创建使用当前定义的 Agent。需要保留原预设组合时,继续使用已有 Session。
|
||||
2. 重连工作区的客户端集成应使用 `openWorkspace` 或 `connectWorkspace`。准备已有草稿时保留显式草稿准备选项。
|
||||
3. 确认新 Agent 拥有已选择的工具;无需重写持久化 Session 文件或 profile patch。
|
||||
3. 确认新 Agent 使用更新后的预设定义;无需重写持久化 Session 文件或 profile patch。
|
||||
@@ -1,16 +0,0 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/preset-bundle-rows/guide.md
|
||||
/:
|
||||
en: 25de822f0f884eb9
|
||||
zh: c910706695f91c76
|
||||
/identify-bundle-rows-by-their-preset:
|
||||
en: 95cdaba0de8fe664
|
||||
zh: f7382ff6b9f65a49
|
||||
/identify-bundle-rows-by-their-preset/change:
|
||||
en: e43402c18f6b6de8
|
||||
zh: 32f3e46546e9d1f8
|
||||
/identify-bundle-rows-by-their-preset/migration:
|
||||
en: 89cead6b9cd199bd
|
||||
zh: 8a13a234d9feacbb
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Plugin-manager bundle inventories include preset-scoped rows whose row IDs are unique only within their preset."
|
||||
---
|
||||
|
||||
# Identify bundle rows by their preset
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
The next release adds preset-scoped declarations to `PluginManager.listBundles().rows`. A bundle can declare the same `rowId` in several presets. `PluginRowRef` values passed to detail slots carry the same optional `preset` identity. These rows carry `preset`, optional `composition` state, and `readOnlyReason: 'preset-managed'`; they do not carry an editable root `entryId`.
|
||||
|
||||
Clients that key rows only by bundle name and `rowId`, or treat every row as a root plugin switch, must distinguish preset declarations. Existing root rows retain their current identity and controls.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Include `preset` in row identity, alongside the bundle name and `rowId`. Keep the absent-preset case distinct from every named preset.
|
||||
2. Render `preset-managed` rows read-only in direct row controls. Use the bundle switch or an explicit scoped profile patch to configure them. Only pass a returned root `entryId` to `setPluginEnabled`.
|
||||
3. Read preset row state from `composition`, instead of looking up a root Loader entry with the same `rowId`.
|
||||
4. For YAML edits, use `preset` to address the outer declaration and `id` to address its child:
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
id: optional-tool-str-replace-editor
|
||||
disabled: true
|
||||
```
|
||||
|
||||
5. Verify that a bundle contributing to standard, cordis, and ptc shows three distinct rows and that root-row switches continue to work. The [profile patch reference](../../../../packages/boot/app-boot/README.md) describes ordering and validation.
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "插件管理器的组合包清单包含预设作用域行,其行 ID 仅在所属预设内唯一。"
|
||||
---
|
||||
|
||||
# 按所属预设区分组合包行
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
下一个版本在 `PluginManager.listBundles().rows` 中加入预设作用域声明。一个组合包可以在多个预设中声明相同的 `rowId`。详情插槽中的 `PluginRowRef` 也携带可选的 `preset` 标识。这些行包含 `preset`、可选的 `composition` 状态和 `readOnlyReason: 'preset-managed'`,不包含可编辑的根级 `entryId`。
|
||||
|
||||
仅用组合包名和 `rowId` 作为行标识,或将所有行都视为根插件开关的客户端,必须区分预设声明。已有根行保留当前标识和控制方式。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 将 `preset` 与组合包名、`rowId` 一起纳入行标识。区分没有预设的情况与每个具名预设。
|
||||
2. 在直接行控制中只读展示 `preset-managed` 行。通过组合包开关或显式的预设作用域 profile patch 配置这些行。只将实际返回的根级 `entryId` 传给 `setPluginEnabled`。
|
||||
3. 从 `composition` 读取预设行状态,不要按同一个 `rowId` 查找根 Loader 条目。
|
||||
4. 编辑 YAML 时,用 `preset` 定位外层声明,用 `id` 定位其子行:
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
id: optional-tool-str-replace-editor
|
||||
disabled: true
|
||||
```
|
||||
|
||||
5. 确认同时向 standard、cordis 和 ptc 贡献行的组合包显示三个不同的行,且根行开关仍可使用。[Profile patch 参考](../../../../packages/boot/app-boot/README.zh.md)说明顺序与校验规则。
|
||||
@@ -0,0 +1,16 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md
|
||||
/:
|
||||
en: d154c0e25274bb2f
|
||||
zh: 7e59bdf067d2b141
|
||||
/agent-teams-messages-use-target-inboxes:
|
||||
en: 04d7dcbdc1679eae
|
||||
zh: 8b083641a9262d80
|
||||
/agent-teams-messages-use-target-inboxes/change:
|
||||
en: 9968decf3dd340af
|
||||
zh: 736a32caac4b5103
|
||||
/agent-teams-messages-use-target-inboxes/migration:
|
||||
en: 7ffd442bfd55769e
|
||||
zh: ffb11a143f11ce7f
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Agent Teams removes its pending-message limit and stops delivering historical queued mail."
|
||||
---
|
||||
|
||||
# Agent Teams messages use target inboxes
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
The experimental Agent Teams plugin sends new messages directly to the target Agent inbox instead of retaining a separate Team mailbox. `send_message` returns only `{ "sent": true }`; it exposes neither a message ID nor an `accepted` or `queued` status. Acceptance does not confirm model processing or a synchronous storage flush. Failed sends are not retained for retry, and explicit resends are not deduplicated.
|
||||
|
||||
The `maxPendingMessagesPerMember` configuration key is removed. Historical `team/message/queued` and `team/message/delivered` records remain readable, but queued messages that were not delivered before upgrading are never dispatched. Roster and task state remain durable.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Before upgrading, let pending Team messages finish and confirm that their targets received them. After upgrading, explicitly resend any required undelivered message; check the target's history first to avoid duplicates. Do not edit historical Session records.
|
||||
2. Remove `maxPendingMessagesPerMember` from the `@deepseek-ai/dsh-experimental-agent-team` plugin configuration in your `cordis.yml` or overlays. There is no replacement Team queue limit; `maxMessageBytes` still bounds each complete sender-framed message.
|
||||
3. Update any consumer of `send_message` results to read `sent` instead of message IDs or delivery statuses, and handle send errors explicitly. Do not treat success as completed target work. The `ctx.agentTeams.sendMessage()` service still returns `messageId`.
|
||||
4. Start your Team-enabled profile, send a message to another member, and confirm that the tool returns `{ "sent": true }` and that the target receives it. Consult the [package reference](../../../../packages/experimental/agent-team/README.md#use-this-package) for delivery semantics.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Agent Teams 移除待投递消息数量限制,并停止投递历史排队消息。"
|
||||
---
|
||||
|
||||
# Agent Teams 消息使用目标收件箱
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
实验性 Agent Teams 插件将新消息直接发送到目标 Agent 收件箱,不再保留独立的 Team 邮箱。`send_message` 仅返回 `{ "sent": true }`,不暴露消息 ID,也不返回 `accepted` 或 `queued` 状态。接收成功不表示模型已处理消息,也不保证同步刷新到存储。失败的发送不会保留以供重试,显式重发也不会去重。
|
||||
|
||||
配置键 `maxPendingMessagesPerMember` 已移除。历史 `team/message/queued` 和 `team/message/delivered` 记录仍可读取,但升级前尚未投递的排队消息永远不会被派发。成员名单与任务状态仍然持久保存。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 升级前,让待投递的 Team 消息完成投递,并确认目标已收到。升级后,显式重发仍需处理的未投递消息;先检查目标历史,避免重复。不要编辑历史 Session 记录。
|
||||
2. 从 `cordis.yml` 或覆盖配置中 `@deepseek-ai/dsh-experimental-agent-team` 插件的配置移除 `maxPendingMessagesPerMember`。没有替代的 Team 队列限制;`maxMessageBytes` 仍限制包含发送者标识的完整消息大小。
|
||||
3. 更新所有读取 `send_message` 结果的调用方,改为读取 `sent` 而非消息 ID 或投递状态,并显式处理发送错误。不要把发送成功视为目标工作已完成。`ctx.agentTeams.sendMessage()` 服务仍返回 `messageId`。
|
||||
4. 启动启用 Team 的 profile,向其他成员发送消息,确认工具返回 `{ "sent": true }` 且目标收到消息。投递语义参见[包参考](../../../../packages/experimental/agent-team/README.zh.md#use-this-package)。
|
||||
@@ -24,8 +24,8 @@
|
||||
en: 1099528bde0e2aee
|
||||
zh: b68772bdb9bb7856
|
||||
/deepseek-ai-dsh-app-boot/use-this-package/profiles:
|
||||
en: 1db121100677e45f
|
||||
zh: c6a8c6316020eb1b
|
||||
en: d50b0fdaf62353dd
|
||||
zh: 0fccb6968602e5de
|
||||
/deepseek-ai-dsh-app-boot/use-this-package/previewing-the-effective-configuration:
|
||||
en: 646a0ccdd7881ad1
|
||||
zh: e3696acb1573dd7b
|
||||
@@ -33,11 +33,11 @@
|
||||
en: c4a301652b851ce9
|
||||
zh: 784871e19cd9db0a
|
||||
/deepseek-ai-dsh-app-boot/use-this-package/reading-plugin-display-metadata:
|
||||
en: ebf6cb3960a5b378
|
||||
zh: 41ba129a0f86c168
|
||||
en: a9afb263b03d112c
|
||||
zh: 31f5e797cd953c15
|
||||
/deepseek-ai-dsh-app-boot/use-this-package/startup-and-reload-failures:
|
||||
en: b2e5cfdf8847d214
|
||||
zh: 8d65771a3bd8a7c9
|
||||
en: 09890e218ab49c84
|
||||
zh: e3d86597e7fb1adb
|
||||
/deepseek-ai-dsh-app-boot/use-this-package/telling-the-agent-where-the-harness-lives:
|
||||
en: 07c459f26f4f8517
|
||||
zh: 210f1e4894731b1a
|
||||
|
||||
@@ -62,21 +62,7 @@ Your machine-local preferences also live in the Harness home:
|
||||
|
||||
The enabled `dsh-hmr` plugin watches the profile manifest and both user patch files, re-reads the ordered bundle layers, and applies the [reload failure policy](#startup-and-reload-failures). [DSH HMR](../hmr/README.md) serializes these reloads with [Plugin Manager](../plugin-manager/README.md) configuration writes; package operations run outside its queue. The launcher does not install HMR or watchers; disabled or absent HMR means changes require restart.
|
||||
|
||||
Inserted plugin names may be absolute filesystem paths, file URLs, or package specifiers. Patch loading converts absolute paths and patch-relative `./` or `../` paths to file URLs within `insert` rows, their nested groups, and inserted preset definitions; existing-entry name assertions and replacement `config` values remain literal.
|
||||
|
||||
A profile patch may set `preset` to an outer row id and apply the remaining ordinary patch fields inside that row's literal `config.plugins` list. Any bundle or user layer can contribute these operations. Each operation sees the current child list; later user operations take precedence, and a later whole-preset `config` replacement replaces every earlier child contribution. Ordinary outer patches retain Include's single-index lookup: children introduced by replacing a group's `config` are not addressable outer targets. Missing or malformed outer preset targets and duplicate child ids introduced by an insertion fail composition. An unmatched inner `id` warns and skips, so disabling a bundle does not invalidate an old user override of its child.
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
insert:
|
||||
- id: optional-string-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
- preset: preset-standard
|
||||
id: optional-string-editor
|
||||
disabled: true
|
||||
```
|
||||
|
||||
`ProfilePatch` extends the native patch fields with `preset`. `applyProfilePatches` composes these operations; `compileProfilePatches` returns ordinary Include patches. Startup, profile reload, effective-config dumps, schema inspection, and compatibility preflight share this compiler. Scoped `insert` module paths resolve beside their declaring patch, including nested groups; `!!js` remains unevaluated until each child activates. Native included-file patch lists keep their ordinary Include syntax. Loaded scoped insertions without IDs receive stable internal IDs derived from the canonical declaring file and entry position, including nested groups; ordinary anonymous root rows keep Loader’s existing behavior. Generated IDs identify contributions across reloads and retained revisions, and are implementation details rather than names to use in authored patches. Compiler failures name the declaring patch file and operation, including after profile layers are combined.
|
||||
Inserted plugin names may be absolute filesystem paths, file URLs, or package specifiers. Patch loading converts absolute paths and patch-relative `./` or `../` paths to file URLs within `insert` rows and their nested groups; existing-entry name assertions and replacement `config` values remain literal.
|
||||
|
||||
Before mounting profile rows, the `dsh` launcher computes one immutable runtime resolution from the installation and ordered bundle dependency graphs. Every profile launcher uses runtime resolution, including plain Node, packaged executables, and the Electron Host. It installs the runtime resolution through Node's ESM and CommonJS resolvers without creating fallback links.
|
||||
|
||||
@@ -98,8 +84,6 @@ The projector preserves native omission behavior by checking literal defaults ag
|
||||
|
||||
`resolvePluginResource(specifier, parentURL)` resolves a plugin module or exported resource to its local filesystem path through the active Node ESM resolver without evaluating it. It throws when the resolver is unavailable or the resource cannot resolve to a local file.
|
||||
|
||||
`realModuleFile(path)` canonicalizes an existing module resource in Node and packaged executables. It follows physical file symlinks and preserves archive-backed resources through their canonical containing directory. Missing or inaccessible resources throw.
|
||||
|
||||
Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifier, parentURL)` to read installed package display text without importing or activating the plugin. Lookup uses the complete package specifier and the caller's resolution base, respecting Node exports. File paths and file URLs return no metadata without resolving resources. For a package-root specifier, missing locale fields fall back to the accessible `package.json`; a subpath specifier never reads a `package.json`. Malformed metadata returns an `error` diagnostic. Results retain translations for Client-side language selection. The reader also loads an image data URL, even when locale text is complete: a package root uses its manifest `icon`, or `<package>/icon` when that field is omitted; a subpath uses `<specifier>/icon`. An icon error preserves valid text alongside the diagnostic. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format.
|
||||
|
||||
`ON_DEMAND_BUNDLES` names the public Official packages installed only on request. `OFFICIAL_ON_DEMAND_CATALOG` embeds their package-owned localized metadata and icons for offline discovery; `pnpm gen-official-bundle-catalog` explicitly updates generated content, and `verify-official-bundle-catalog` checks resource completeness and freshness; builds consume the committed catalog without importing provider code. The catalog contains no install target: [Plugin Manager](../plugin-manager/README.md#use-this-package) derives it from the running version and installation and uses the ordinary bundle installer. The [product-use check](../../../scripts/verify-product-use.ts) composes shipped and on-demand selections against Web without executing providers.
|
||||
@@ -107,7 +91,7 @@ Use `readPluginMeta(specifier, parentURL)` or `ctx.pluginPackages.metaOf(specifi
|
||||
<a id="startup-and-reload-failures"></a>
|
||||
### Startup and reload failures
|
||||
|
||||
Profile reconciliation returns diagnostics for unchanged inactive entries without failing an unrelated mutation. A new inactive entry, a changed configuration or fiber, or a changed diagnostic fails reconciliation; removed fibers must still finish disposal. Active root rows that disappear or become literally disabled release their resources before replacement rows activate, so a singleton provider can drain in-flight work before its successor registers. Compilation and disposal checks use the same parsed root entries for each reconciliation. Explicit enablement targets must activate even when their failure predates the operation. Successful reconciliation emits `app-boot/config-reload` after lifecycle settlement and diagnostic checks, including programmatic updates without HMR. The event carries no diff or parsed config. Successful reconciliation returns after lifecycle settlement and diagnostic checks; volatile-only entry changes are committed by Loader during the update.
|
||||
Profile reconciliation returns diagnostics for unchanged inactive entries without failing an unrelated mutation. A new inactive entry, a changed configuration or fiber, or a changed diagnostic fails reconciliation; removed fibers must still finish disposal. Explicit enablement targets must activate even when their failure predates the operation. Successful reconciliation emits `app-boot/config-reload` after lifecycle settlement and diagnostic checks, including programmatic updates without HMR. The event carries no diff or parsed config. Successful reconciliation returns after lifecycle settlement and diagnostic checks; volatile-only entry changes are committed by Loader during the update.
|
||||
|
||||
After the Loader settles, app-boot warns when only optional entries are inactive. If an enabled required entry cannot activate, `boot()` rejects with `StartupError` after disposal. An independently owned logger exporter retains warning and error records through asynchronous disposal and is released before `boot()` settles. Its message groups all failed plugins and pending services, marks required entries, and retains original stacks, nested causes, and aggregate members. The CLI prints that message once and saves [full startup diagnostics](../../../apps/cli/reference/README.md#startup-diagnostics) before exiting with code 1; unrelated exceptions retain their normal stack output. In the table, stopping startup means disposing any mounted plugins and exiting nonzero without reporting readiness; continuing keeps successful plugins running. Later configuration HMR does not repeat the required-startup audit and does not roll back the whole update.
|
||||
|
||||
|
||||
@@ -62,21 +62,7 @@ profile 导入插件前,DSH 会检查其 `peerDependencies` 中对 `@deepseek-
|
||||
|
||||
启用的 `dsh-hmr` 插件会监视 profile manifest 与两份用户 patch 文件,重新读取按顺序排列的组合包层,并应用[重载失败策略](#startup-and-reload-failures)。[DSH HMR](../hmr/README.zh.md) 将这些重载与[插件管理器](../plugin-manager/README.zh.md)的配置写入串行化;包操作在其队列之外执行。启动器不安装 HMR 或监视器;HMR 被禁用或不存在时,更改需要重启。
|
||||
|
||||
插入条目的插件名可以是绝对文件系统路径、文件 URL 或包标识符。patch 加载会把 `insert` 条目、其嵌套分组和插入的 preset 定义中的绝对路径,以及相对于 patch 文件的 `./` 或 `../` 路径转换为文件 URL;对已有条目名称的断言及替换用的 `config` 值保持原样。
|
||||
|
||||
profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch 字段应用到该条目的字面量 `config.plugins` 列表中。任何 bundle 或用户层都可以提供这些操作。每个操作作用于当前子条目列表;较晚的用户操作优先,而较晚的整个 preset `config` 替换会覆盖此前所有子条目贡献。普通外层 patch 保留 Include 的单次索引查找语义:替换 group 的 `config` 所引入的子条目无法作为外层目标。外层 preset 目标缺失或格式错误、插入造成子条目 id 重复都会使组合失败。内层 `id` 不匹配时只警告并跳过,因此禁用 bundle 不会使用户保留的子条目覆盖失效。
|
||||
|
||||
```yaml
|
||||
- preset: preset-standard
|
||||
insert:
|
||||
- id: optional-string-editor
|
||||
name: '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
- preset: preset-standard
|
||||
id: optional-string-editor
|
||||
disabled: true
|
||||
```
|
||||
|
||||
`ProfilePatch` 在原生 patch 字段上增加 `preset`。`applyProfilePatches` 组合这些操作;`compileProfilePatches` 返回普通 Include patch。启动、profile 重载、有效配置 dump、schema 检查及兼容性预检共用此编译器。限定到 preset 的 `insert` 模块路径以声明它的 patch 所在目录为基准,包括嵌套分组;`!!js` 保持不求值,直至对应子条目激活。原生被 Include 引入文件的 patch 列表仍使用普通 Include 语法。 读取的作用域插入行若未声明 ID,会依据声明文件的规范路径及条目位置获得稳定的内部 ID,嵌套组内的行也如此;普通匿名根行保留 Loader 的既有行为。生成的 ID 用于在重载和保留代际之间识别贡献项,是实现细节,不是作者应在 patch 中引用的名称。编译失败会标明声明 patch 文件和操作序号,profile 层合并后也保留这些信息。
|
||||
插入条目的插件名可以是绝对文件系统路径、文件 URL 或包标识符。patch 加载会把 `insert` 条目及其嵌套分组中的绝对路径以及相对于 patch 文件的 `./` 或 `../` 路径转换为文件 URL;对已有条目名称的断言及替换用的 `config` 值保持原样。
|
||||
|
||||
挂载 profile 条目前,`dsh` launcher 会从安装依赖图与有序 bundle 依赖图计算一份不可变的 runtime resolution。普通 Node、打包可执行文件与 Electron Host 等所有 profile 启动器都使用 runtime 解析,将 runtime resolution 安装到 Node 的 ESM 与 CommonJS 解析器中,不创建 fallback 链接。
|
||||
|
||||
@@ -98,8 +84,6 @@ profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch
|
||||
|
||||
`resolvePluginResource(specifier, parentURL)` 通过活动的 Node ESM 解析器将插件模块或导出资源解析为本地文件路径,不执行其代码。解析器不可用或资源无法解析为本地文件时会抛出错误。
|
||||
|
||||
`realModuleFile(path)` 在 Node 和打包可执行程序中规范化已有模块资源的路径。它跟随物理文件符号链接,并通过规范化后的所在目录保留归档资源。资源不存在或无法访问时抛出错误。
|
||||
|
||||
使用 `readPluginMeta(specifier, parentURL)` 或 `ctx.pluginPackages.metaOf(specifier, parentURL)` 读取已安装包的展示文本,无需导入或激活插件。查询使用完整包标识与调用方的解析基准,并遵循 Node exports。文件路径与文件 URL 不解析资源,直接返回无元信息。包根标识缺失的 locale 字段回退到可访问的 `package.json`;子路径标识从不读取 `package.json`。格式错误的元信息返回 `error` 诊断。结果保留翻译,由 Client 选择语言。即使 locale 文本完整,读取器也会加载图片 data URL:包根使用清单 `icon`,省略该字段时使用 `<包名>/icon`;子路径使用 `<标识>/icon`。图标出错时,保留有效文本并附上诊断。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。
|
||||
|
||||
`ON_DEMAND_BUNDLES` 列出仅按需安装的官方公开包。`OFFICIAL_ON_DEMAND_CATALOG` 内嵌这些包拥有的本地化元信息和图标,支持离线发现;`pnpm gen-official-bundle-catalog` 显式更新生成内容,`verify-official-bundle-catalog` 检查资源完整性与新鲜度;构建只读取已提交的目录,不导入提供者代码。目录不保存安装目标;[插件管理器](../plugin-manager/README.zh.md#use-this-package)根据当前版本和安装位置确定目标,并使用普通组合包安装器。[产品用途检查](../../../scripts/verify-product-use.ts)将随附和按需选择与 Web 组合,但不执行提供者。
|
||||
@@ -107,7 +91,7 @@ profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch
|
||||
<a id="startup-and-reload-failures"></a>
|
||||
### 启动与重载失败
|
||||
|
||||
profile 重载返回未变化的已有故障诊断,不让无关修改因此失败。新增未激活条目、配置或 fiber 变化、诊断变化都会使重载失败;被移除的 fiber 仍须完成释放。消失或被字面量禁用的活跃根条目先释放资源,再激活替代条目,因此单实例提供方可以先排空进行中的工作,再让后继者注册。每次重组时,编译与释放检查使用同一份已解析的根条目。显式启用的目标必须成功激活,即使它的故障早于本次操作。成功重载在生命周期结束及诊断检查通过后发出 `app-boot/config-reload`,包括未启用 HMR 时的程序化更新。事件不携带 diff 或解析后的配置。 成功重载在生命周期结束及诊断检查通过后返回;仅 volatile 的条目变化由 Loader 在更新过程中提交。
|
||||
profile 重载返回未变化的已有故障诊断,不让无关修改因此失败。新增未激活条目、配置或 fiber 变化、诊断变化都会使重载失败;被移除的 fiber 仍须完成释放。显式启用的目标必须成功激活,即使它的故障早于本次操作。成功重载在生命周期结束及诊断检查通过后发出 `app-boot/config-reload`,包括未启用 HMR 时的程序化更新。事件不携带 diff 或解析后的配置。 成功重载在生命周期结束及诊断检查通过后返回;仅 volatile 的条目变化由 Loader 在更新过程中提交。
|
||||
|
||||
Loader 结算后,app-boot 在仅 optional 条目未激活时输出警告。如果已启用的 required 条目无法激活,`boot()` 会在释放资源后以 `StartupError` 拒绝。独立管理生命周期的 logger exporter 会保留异步资源释放期间的警告和错误记录,并在 `boot()` 结算前释放。其消息分组列出所有失败插件和等待的服务,标记 required 条目,并保留原始堆栈、嵌套原因和聚合错误成员。CLI 仅输出该消息一次,并在保存[完整启动诊断](../../../apps/cli/reference/README.zh.md#startup-diagnostics)后以退出码 1 结束;其他异常保留正常堆栈输出。表中的“终止启动”指释放已挂载插件并以非零码退出,不报告就绪;“继续”指保留成功运行的插件。后续配置 HMR 不会再次执行 required 启动审计,也不会回滚整个更新。
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
||||
import { applyEntryPatches, entryListSchema, type PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
||||
import { load } from 'js-yaml'
|
||||
import { applyProfilePatches, type ProfilePatch } from './profile-patches.ts'
|
||||
import { resolvePluginResource } from './package-meta.ts'
|
||||
import { barePackageName } from './profile-resolution/resolver.ts'
|
||||
import type {} from './profile-resolution/service.ts'
|
||||
@@ -179,10 +178,10 @@ function preflight(
|
||||
* @returns One prepared insertion patch for profiles, or the original patches for non-profile callers.
|
||||
*/
|
||||
export function prepareProfilePatches(
|
||||
ctx: Context, patches: ProfilePatch[], parentURL: string, binName = 'dsh',
|
||||
ctx: Context, patches: PatchOptions[], parentURL: string, binName = 'dsh',
|
||||
): PatchOptions[] {
|
||||
if (ctx.get('profileContext') === undefined) return patches
|
||||
const entries = applyProfilePatches([], patches, patchWarning(ctx))
|
||||
const entries = applyEntryPatches([], patches, patchWarning(ctx))
|
||||
const rows = prepareProfileEntries(ctx, entries, parentURL, binName)
|
||||
return rows.length === 0 ? [] : [{ insert: rows }]
|
||||
}
|
||||
@@ -143,23 +143,13 @@ export async function buildConfigSchemaDocument(
|
||||
required: ['id'], properties: { id: { const: id } },
|
||||
allOf: [
|
||||
{ not: { required: ['insert'] } },
|
||||
{ not: { required: ['preset'] } },
|
||||
{ anyOf: [{ not: { required: ['name'] } }, { properties: { name: { enum: ['', target.name] } } }] },
|
||||
],
|
||||
},
|
||||
then: { properties: { config: { $ref: configRef } } },
|
||||
})
|
||||
}
|
||||
definitions.patch = {
|
||||
...patchStructure(), allOf: [ref('entryMetadata'), ...patchRules],
|
||||
properties: {
|
||||
insert: ref('entryList'),
|
||||
preset: {
|
||||
type: 'string', pattern: '\\S',
|
||||
description: 'Outer preset row id. The remaining patch fields apply to its current config.plugins list; a missing or unaddressable preset fails composition.',
|
||||
},
|
||||
},
|
||||
}
|
||||
definitions.patch = { ...patchStructure(), allOf: [ref('entryMetadata'), ...patchRules] }
|
||||
const positions = new Map(entries.map((entry, index) => [entry.path, index]))
|
||||
diagnostics.sort((left, right) => (positions.get(left.path ?? '') ?? -1) - (positions.get(right.path ?? '') ?? -1))
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** Profile schema generation: composition diagnostics, runtime resolution, and boot-free discovery. */
|
||||
|
||||
import type { ProfilePatch } from '../profile-patches.ts'
|
||||
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
||||
import { composeEntries, createRuntimeResolution, type Profile } from '../profile.ts'
|
||||
import { collectConfigSchemas } from './collect.ts'
|
||||
import type { ConfigSchemaDiagnostic, ConfigSchemaDump } from './types.ts'
|
||||
@@ -20,7 +20,7 @@ export type { ConfigSchemaDump, NativeConfigSchema } from './types.ts'
|
||||
*/
|
||||
export async function generateConfigSchema(
|
||||
profile: Profile,
|
||||
layers: readonly ProfilePatch[][],
|
||||
layers: readonly PatchOptions[][],
|
||||
installAnchor: string,
|
||||
): Promise<ConfigSchemaDump> {
|
||||
const diagnostics: ConfigSchemaDiagnostic[] = []
|
||||
|
||||
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user