diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md index 9425a652cb..d529b27542 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md @@ -16,6 +16,8 @@ An optional bundle's dependencies are downloaded with every `dsh` installation. 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. +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. + ## Alternatives considered **Ship every provider as an optional bundle.** Composes and displays correctly, but grows every install by provider runtimes for a capability most installations never switch on; voice input's `sherpa-onnx-node` is the one accepted precedent. diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md index 845dcbc849..13abff45a0 100644 --- a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md +++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md @@ -16,6 +16,8 @@ Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto r Bundle 通过 [profile patch 操作](../../../../packages/boot/app-boot/README.zh.md)添加作用域行:`preset` 指定声明行,普通 patch 对其子列表生效。完整 Web 预设获得可选模型工具;minimal 保持当前的 shell 与工作目录工具。搜索、Ralph 与终端提供自己的隔离服务。搜索在每个保留的预设修订中按需打开一个内存索引;Host 搜索策略保持不变。徽章技能与标题提供方作用于 Host。已有 Agent 保留其预设代际,后续用户 patch 层继续优先。标题替换等待前一个提供方完成清理。 +Claude Code 与 Codex 通过独立的 `ON_DEMAND_BUNDLES` 目录按需安装,其成本、版本与离线发现取舍由[按需原生 bundle 决策](2026-10-05-official-on-demand-bundles.zh.md)管理。 + ## 考虑过的替代方案 **把所有提供方都做成可选 bundle。** 组合与展示都正确,但会为多数安装从不启用的能力把提供方运行时塞进每次安装;语音输入的 `sherpa-onnx-node` 是唯一被接受的先例。 diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml new file mode 100644 index 0000000000..8c09f71e53 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-05-official-on-demand-bundles.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-05-official-on-demand-bundles.md +/agent-note-discover-official-bundles-without-installing-their-runtimes: + en: 1f2387f10628638b + zh: c835a8ee615e9da6 +/agent-note-discover-official-bundles-without-installing-their-runtimes/problem: + en: a590bcef6c9760c0 + zh: 19e6f61c2c7d8d3b +/agent-note-discover-official-bundles-without-installing-their-runtimes/decision: + en: 9a3c680dbd29e97e + zh: 58609ec8232dc92f +/agent-note-discover-official-bundles-without-installing-their-runtimes/alternatives-considered: + en: 4c611d42f0bb3d02 + zh: f3a368c3757c488b +/agent-note-discover-official-bundles-without-installing-their-runtimes/consequences: + en: 5db0d899112d2c8c + zh: e9962086d938d5a2 diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md new file mode 100644 index 0000000000..f86e50c363 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.md @@ -0,0 +1,31 @@ +# Agent Note: Discover Official bundles without installing their runtimes + +Status: implemented + +English | [中文](2026-10-05-official-on-demand-bundles.zh.md) + +## Problem + +Native subagent integrations bring their own runtimes. Shipping those dependencies with every DSH installation charges that cost to people who never delegate to them, while requiring a package name hides the integrations from the Plugins page. + +## Decision + +`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. The catalog supplies the exact running DSH version and saves an exact dependency pin. An application upgrade offers an explicit package update; it does not download providers at startup. Native authentication and configuration remain provider-owned. + +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. + +## Alternatives considered + +**Ship the native runtimes with every installation.** This makes first activation offline, but increases the default download for unused integrations. + +**Query the registry to build the discovery list.** This avoids embedded metadata, but makes discovery depend on network availability and does not bind the offered package version to the running application. + +**Give Official packages a separate installer or activation registry.** A second path would duplicate package-operation and profile-composition behavior. Installation-owned discovery data can reuse the existing third-party mechanisms. + +## Consequences + +First installation and explicit updates require a reachable registry. 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, exact pins, clean installed artifacts, and release ordering have dedicated checks, while native-provider authentication is not part of catalog discovery. diff --git a/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md new file mode 100644 index 0000000000..48d517fb60 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-05-official-on-demand-bundles.zh.md @@ -0,0 +1,31 @@ +# Agent Note: 发现 Official bundle 而不安装其运行时 + +Status: implemented + +[English](2026-10-05-official-on-demand-bundles.md) | 中文 + +## 问题 + +原生子智能体集成携带自己的运行时。把这些依赖放进每次 DSH 安装,会让从不向它们委派任务的用户也承担成本;要求用户知道包名,又会让插件页无法展示这些集成。 + +## 决策 + +`ON_DEMAND_BUNDLES` 将 Claude Code 和 Codex bundle 纳入安装方维护的目录。目录内嵌包自身拥有的本地化元信息和图标,因此发现过程离线可用,无需解析或导入这两个包。Official 表示项目维护;可选交付与实验成熟度仍是独立属性。 + +选择操作复用普通第三方 bundle 的安装器、profile 依赖、作用域 patch、兼容性判断、进度、取消、注册表回退和构建脚本审批。目录提供当前运行 DSH 的精确版本,并保存精确依赖版本。应用升级后提供显式包更新,不在启动时下载提供方。原生认证与配置仍由提供方拥有。 + +所有 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 显式挂载提供方和工具。 + +## 考虑过的替代方案 + +**每次安装都附带原生运行时。** 首次激活可离线完成,但会为未使用的集成增加默认下载量。 + +**查询注册表构建发现列表。** 可以避免内嵌元信息,但发现过程依赖网络可用性,且无法把提供的包版本绑定到运行中的应用。 + +**为 Official 包增加独立安装器或激活注册表。** 第二条路径会重复包操作和 profile 组合行为。安装方维护的发现数据可以复用已有第三方机制。 + +## 后果 + +首次安装和显式更新要求注册表可达。关闭仅取消选择;移除保持独立,并尊重预设仍保留的模块。激活失败可以保留已保存的依赖和选择,不加入回滚。离线元信息、精确版本、干净安装产物和发布顺序有专门检查,原生提供方认证不属于目录发现过程。 diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml index d0ca4b49ee..e3ffc60e70 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml @@ -9,11 +9,11 @@ en: dc43ba84345e1405 zh: dbc03572ab7710d2 /agent-note-ship-optional-bundles-with-the-installation/decision: - en: 92fd1e2e9345e407 - zh: 33a39f71413499ff + en: 5210b3b746cfee78 + zh: f1907f4807ae0098 /agent-note-ship-optional-bundles-with-the-installation/alternatives-considered: - en: 05c19f616009bee8 - zh: af318640481351f0 + en: 95b48b61757f1f13 + zh: e9fb2d6ea492a4bc /agent-note-ship-optional-bundles-with-the-installation/consequences: en: 4a9d3a049a605150 zh: 67b50bbc9f7f4bb0 diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md index bf5d2206ef..151cc8ccad 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md @@ -10,15 +10,15 @@ The Web plugin page manages only the bundles a person installed into the profile ## Decision -The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts`, beside the profile templates) the bundles the installation ships for a person to switch on. Each must be a runtime dependency of `apps/cli` that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page opens its Official group with the optional bundles, tagged beta where the feature is one, ahead of the profile's own installed bundles. +The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts`, beside the profile templates) the bundles the installation ships for a person to switch on. Each must be a runtime dependency of `apps/cli` that declares `dsh.bundle.patch`, and no shipped profile template selects it. The plugin manager's `listBundles` reports such a bundle as `optional`: switched off until selected, never removable, resolved from the installation like any installation-supplied bundle. The Web plugin page opens its Official group with the optional bundles, tagged experimental when the package is experimental, ahead of the profile's own installed bundles. Default-product isolation keeps its rules with one declared exception: an optional bundle's dependency graph is outside the default product. The static gate skips the `dependencies` edge from `@deepseek-ai/dsh` to a listed bundle and still rejects a runtime import, a shipped composition, a preset, or a default template that names it, an experimental dependency the list does not name, and a listed name that is not a runtime dependency or not a bundle. The workspace-constraints check accepts the same `dependencies` edges and no other runtime section, and the packed-install release check skips them from the installed entry package while requiring each listed bundle to be installed. -The [single-bundle reference](../../../../packages/experimental/agent-team-profile/README.md) supersedes the two Agent Teams selections with one `@deepseek-ai/dsh-experimental-agent-team-profile` selection. This note retains the installation-owned optional-bundle policy; the [optional-bundle decision for experimental capabilities](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) names the current list and the display metadata each entry carries. +The [single-bundle reference](../../../../packages/experimental/agent-team-profile/README.md) supersedes the two Agent Teams selections with one `@deepseek-ai/dsh-experimental-agent-team-profile` selection. This note retains the installation-owned optional-bundle policy; the [optional-bundle decision for experimental capabilities](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) owns composition and display metadata; `OPTIONAL_BUNDLES` owns the current shipped list. ## Alternatives considered -**A catalog of installable official bundles.** The page would offer names to install from the registry on demand. That keeps the installation unchanged but needs network access at the moment of switching on and a version pin per release. +**A catalog of installable official bundles.** The page would offer names to install from the registry on demand. That keeps the installation unchanged but needs network access at the moment of switching on and a version pin per release. Lightweight bundles retain the shipped policy; the [on-demand native-bundle decision](../architecture/2026-10-05-official-on-demand-bundles.md) owns the exception for costly native runtimes. **A flag on the bundle package.** A `dsh.bundle.optional` declaration would let any published bundle claim a place in the installation; the launcher's own list keeps the choice with the product. diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md index f6254e78d5..9ddd54da88 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md @@ -10,15 +10,15 @@ Web 插件页只管理用户装进 profile 的组合包。像 Agent Teams 这样 ## 决策 -启动器在 `OPTIONAL_BUNDLES`(`packages/boot/app-boot/src/profile.ts`,与 profile 模板并列)里点名安装随附、供用户开启的组合包。每一个都必须是 `apps/cli` 声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页以可选组合包开启「官方」分组,属于 beta 功能的带 beta 标签,排在 profile 自己已安装的组合包之前。 +启动器在 `OPTIONAL_BUNDLES`(`packages/boot/app-boot/src/profile.ts`,与 profile 模板并列)里点名安装随附、供用户开启的组合包。每一个都必须是 `apps/cli` 声明了 `dsh.bundle.patch` 的运行时依赖,且不被任何随附 profile 模板选中。插件管理器的 `listBundles` 把这类组合包报告为 `optional`:选中前保持关闭、永不可卸载、像其他安装提供的组合包一样从安装目录解析。Web 插件页以可选组合包开启「官方」分组,属于实验包的带实验性标签,排在 profile 自己已安装的组合包之前。 默认产品隔离的规则保持不变,只声明一个例外:可选组合包的依赖图在默认产品之外。静态门禁跳过从 `@deepseek-ai/dsh` 到列表中组合包的 `dependencies` 边,仍然拒绝运行时 import、随附组合、preset 或默认模板对它的引用,拒绝列表没有点名的实验依赖,也拒绝不是运行时依赖或不是组合包的列表项。workspace 约束检查接受同样的 `dependencies` 边而不接受其他运行时依赖段;发布时的 packed-install 检查对已安装入口包跳过这些边,并要求列表中的每个组合包都已安装。 -[单组合包参考](../../../../packages/experimental/agent-team-profile/README.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)列出当前名单以及每一项携带的显示元数据。 +[单组合包参考](../../../../packages/experimental/agent-team-profile/README.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)定义组合与显示元数据;当前随附名单由 `OPTIONAL_BUNDLES` 管理。 ## 考虑过的替代方案 -**可安装官方组合包目录。** 页面按需提供从注册表安装的包名。安装本身不变,但开启那一刻需要网络,并且每次发布都要钉一个版本。 +**可安装官方组合包目录。** 页面按需提供从注册表安装的包名。安装本身不变,但开启那一刻需要网络,并且每次发布都要钉一个版本。轻量 bundle 仍采用随附策略;[按需原生 bundle 决策](../architecture/2026-10-05-official-on-demand-bundles.zh.md)管理原生运行时成本较高的例外。 **组合包自身的标记。** `dsh.bundle.optional` 声明会让任何已发布的组合包都能自称随安装提供;由启动器自己的列表来决定,选择权留在产品手里。 diff --git a/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md b/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md index d2a4fa4c42..5c879f7836 100644 --- a/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md +++ b/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md @@ -7,5 +7,5 @@ - text: slow-package - button "插件安装引导和示例" - button "安装源 默认安装源" - - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版 + - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版。 - button "安装" diff --git a/apps/web/tests/expected/plugin-install-github/another-way.expected.md b/apps/web/tests/expected/plugin-install-github/another-way.expected.md index e307b73172..feb0fbe8ce 100644 --- a/apps/web/tests/expected/plugin-install-github/another-way.expected.md +++ b/apps/web/tests/expected/plugin-install-github/another-way.expected.md @@ -10,5 +10,5 @@ - text: 填入插件 npm 包名 插件包名即 npm 包名(如 dsh-xxx 或 @作者/插件名),社区插件的 README 安装命令中 dsh plugin add 或 pnpm add 之后的部分。 示例: - code: dsh-plugin-whale-pet - button "填入示例 dsh-plugin-whale-pet": 填入示例 - - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版 + - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版。 - button "安装" [disabled] diff --git a/apps/web/tests/expected/plugin-install-github/mirror.expected.md b/apps/web/tests/expected/plugin-install-github/mirror.expected.md index a264c27eab..e4403baf70 100644 --- a/apps/web/tests/expected/plugin-install-github/mirror.expected.md +++ b/apps/web/tests/expected/plugin-install-github/mirror.expected.md @@ -5,5 +5,5 @@ - /placeholder: 例如 dsh-plugin-whale-pet - button "插件安装引导和示例" - button "安装源 中国大陆镜像源" - - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版 + - note: 请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。 按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版。 - button "安装" [disabled] diff --git a/docs/subsystems/boot.i18n.yaml b/docs/subsystems/boot.i18n.yaml index 6659b288a2..52d584bd1f 100644 --- a/docs/subsystems/boot.i18n.yaml +++ b/docs/subsystems/boot.i18n.yaml @@ -6,5 +6,5 @@ en: 962b4398c9052959 zh: 14471c340a9f6e60 /profile-management/management-records: - en: 459beea3f3d82cd4 - zh: a7b8a2d1ab23fb3e + en: a10d3f358e9b7c8e + zh: 28b0f44589a5020d diff --git a/docs/subsystems/boot.md b/docs/subsystems/boot.md index c5b72cc8ea..be79a823a3 100644 --- a/docs/subsystems/boot.md +++ b/docs/subsystems/boot.md @@ -12,7 +12,7 @@ The [boot package group](../../packages/boot/README.md) owns launcher-provided p `PluginInfo` carries module identity, effective enablement, fiber phase and optional display `meta`, plus a unique `patchId` or a `readOnlyReason`. -`BundleInfo.official` records catalog membership independently of installation. `availability` identifies a readable installation-provided or profile package, or `missing`; `installed` records the profile dependency declaration. An on-demand `installTarget` carries the host-derived exact spec and version. `BundleInfo` also carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies and the installation does not, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time. +`BundleInfo.official` identifies project-maintained shipped optional bundles and on-demand catalog entries independently of installation. `availability` identifies a readable installation-provided or profile package, or `missing`; on-demand catalog entries exclude undeclared transitive copies. `installed` records the profile dependency declaration. An on-demand `installTarget` carries the host-derived exact spec and version. `BundleInfo` also carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies and the installation does not, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time. `InstallBundleOptions.saveExact` passes `--save-exact` to the shared package installer. `InstallBundleOptions.enabled` defaults to true. False installs without selecting the bundle layer. `approvedBuilds` grants persistent script permission to the supplied pending package names before installation. `registry` names the registry asked first; absent, the configured one. diff --git a/docs/subsystems/boot.zh.md b/docs/subsystems/boot.zh.md index 2517ed29ad..93024b965a 100644 --- a/docs/subsystems/boot.zh.md +++ b/docs/subsystems/boot.zh.md @@ -12,7 +12,7 @@ `PluginInfo` 包含模块标识、实际启停状态、fiber 阶段和可选的展示 `meta`,以及唯一的 `patchId` 或 `readOnlyReason`。 -`BundleInfo.official` 记录与安装状态独立的目录身份。`availability` 表示可读取的安装随附包、profile 包或 `missing`;`installed` 记录 profile 的依赖声明。按需条目的 `installTarget` 包含 Host 生成的精确 spec 和版本。`BundleInfo` 还包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供、且安装不提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。 +`BundleInfo.official` 标识项目维护的随附可选组合包和按需目录项,独立于安装状态。`availability` 表示可读取的安装随附包、profile 包或 `missing`;按需目录项不计入未声明的传递依赖副本。`installed` 记录 profile 的依赖声明。按需条目的 `installTarget` 包含 Host 生成的精确 spec 和版本。`BundleInfo` 还包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供、且安装不提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。 `InstallBundleOptions.saveExact` 向共享包安装器传入 `--save-exact`。`InstallBundleOptions.enabled` 默认为 true,false 表示安装但不选择组合包层。`approvedBuilds` 在安装前向指定的待审批包名授予持久脚本权限。`registry` 指定首先询问的注册表;缺省为配置的那个。 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 3d15bf8c7a..6bb491ef7f 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -76,7 +76,7 @@ List plugins or bundles in the current profile, enable or disable them, install }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 249aceafdb..6b2b6f9792 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -80,7 +80,7 @@ }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml index ca0e993235..3944dabcc3 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.i18n.yaml @@ -9,8 +9,8 @@ en: b5c79b41fc34574a zh: 5337c7120bf98f08 /native-subagent-bundles-include-preset-tools/change: - en: f600d720349cc291 - zh: 44843b8537a77cc3 + en: 5b38760d7d29b461 + zh: 2f96fe5df18a2141 /native-subagent-bundles-include-preset-tools/migration: - en: a897381647eb8c64 - zh: 6b0d6eb202c5ba9a + en: 0b323199afe74f5d + zh: b87e684d5dfdab57 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md index 3f8bfa2c92..86418d5374 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md @@ -9,11 +9,11 @@ English | [中文](guide.zh.md) ## Change -`@deepseek-ai/dsh-subagent-claude-code` and `@deepseek-ai/dsh-subagent-codex` bundle layers register their providers and add delegation tools to `preset-standard`, `preset-cordis`, and `preset-ptc`. Previously they registered only providers. Selecting these bundles now requires those preset rows; headless, SDK, and ACP profiles do not contain them. +`@deepseek-ai/dsh-subagent-claude-code` and `@deepseek-ai/dsh-subagent-codex` bundle layers register their providers and add delegation tools to `preset-standard`, `preset-cordis`, and `preset-ptc`. Previously they registered only providers. Selecting these bundles now requires those preset rows; headless, SDK, and ACP profiles do not contain them. `dsh plugin add` selects a bundle automatically, and profile startup with missing targets reports `profile patch preset "preset-standard" is missing or not addressable`. The manager likewise reports activation failure and can retain the saved selection; recover below without automatic rollback. The full Web presets omit their dormant `tool-subagent-claude-code` and `tool-subagent-codex` rows. The bundles contribute `optional-tool-subagent-claude-code` and `optional-tool-subagent-codex` instead. Provider configuration and native authentication are unchanged. -`pluginManager.listBundles()` now returns required `BundleInfo.official` and `BundleInfo.availability` fields. `official` identifies catalog membership; `availability` is `installation`, `profile`, or `missing`. `installed` still means a profile dependency declaration. Optional `installTarget` gives the host-selected exact spec and version. Client detail-slot `PluginPackageRef` also requires `official` and `availability`. +`pluginManager.listBundles()` now returns required `BundleInfo.official` and `BundleInfo.availability` fields. `official` identifies project-maintained shipped optional bundles and on-demand catalog entries; `availability` is `installation`, `profile`, or `missing`. `installed` still means a profile dependency declaration. Optional `installTarget` gives the host-selected exact spec and version. Client detail-slot `PluginPackageRef` also requires `official` and `availability`. ## Migration @@ -21,5 +21,16 @@ The full Web presets omit their dormant `tool-subagent-claude-code` and `tool-su 2. Remove profile patches targeting the former dormant row ids. Configure the new rows with a scoped `preset` patch, using the appropriate `optional-tool-subagent-*` id. A configuration override still replaces the complete row configuration. 3. For headless, SDK, ACP, or other profiles without the three preset targets, retain the provider package in `$DSH_HOME/profiles//package.json` dependencies and remove its entry from `dsh.profile.bundles`. Mount the provider and desired generic delegation tool explicitly in that profile's `cordis.patch.yml`; the [Codex](../../../../packages/subagent/subagent-codex/README.md#exposing-the-tool) and [Claude Code](../../../../packages/subagent/subagent-claude-code/README.md#exposing-the-tool) examples show the rows. This preserves the provider-only installation workflow. 4. Open a new Session with the intended preset and confirm its tool catalog includes the selected delegation tool. The minimal preset remains unchanged. Off deselects the bundle without uninstalling it; Remove remains a separate action. - 5. Update clients and fixtures that construct or validate `BundleInfo` or `PluginPackageRef` to include the new required fields. Use `official` for grouping and `availability` for package presence; `installed: false` alone no longer identifies an installation-carried bundle. + + + +**Recover a profile without full presets** + +For a profile with the provider packages already installed, run this before its next launch; replace `headless` with the intended profile name. The command does not boot the profile. It deselects only these two bundle layers and preserves their packages and dependency versions: + +```sh +dsh plugin --profile headless exec node -e 'const fs=require("node:fs");const p="package.json";const m=JSON.parse(fs.readFileSync(p,"utf8"));m.dsh.profile.bundles=m.dsh.profile.bundles.filter(n=>!["@deepseek-ai/dsh-subagent-codex","@deepseek-ai/dsh-subagent-claude-code"].includes(n));fs.writeFileSync(p,JSON.stringify(m,null,2)+"\n");' +``` + +Then add the desired provider README's “Exposing the tool” `insert` operation to that profile's `cordis.patch.yml`, and run `dsh --profile headless --dump-config` to check the effective composition. The shared base already provides Job services; do not insert them twice. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md index 32a948cb08..e837ebf82c 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md +++ b/docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md @@ -9,11 +9,11 @@ description: "Claude Code 和 Codex bundle 为完整 Web 预设添加委派工 ## 变更 -`@deepseek-ai/dsh-subagent-claude-code` 和 `@deepseek-ai/dsh-subagent-codex` 的 bundle 层会注册提供方,并为 `preset-standard`、`preset-cordis` 和 `preset-ptc` 添加委派工具。此前它们只注册提供方。现在选择这些 bundle 时要求上述预设行存在;headless、SDK 和 ACP 配置不包含这些行。 +`@deepseek-ai/dsh-subagent-claude-code` 和 `@deepseek-ai/dsh-subagent-codex` 的 bundle 层会注册提供方,并为 `preset-standard`、`preset-cordis` 和 `preset-ptc` 添加委派工具。此前它们只注册提供方。现在选择这些 bundle 时要求上述预设行存在;headless、SDK 和 ACP 配置不包含这些行。`dsh plugin add` 会自动选择 bundle,缺少目标时 profile 启动会报 `profile patch preset "preset-standard" is missing or not addressable`。管理器同样报告激活失败,并可保留已保存的选择;按下方步骤恢复,不会自动回滚。 完整 Web 预设不再包含休眠的 `tool-subagent-claude-code` 和 `tool-subagent-codex` 行。Bundle 改为添加 `optional-tool-subagent-claude-code` 和 `optional-tool-subagent-codex`。提供方配置和原生认证保持不变。 -`pluginManager.listBundles()` 现在返回必需的 `BundleInfo.official` 和 `BundleInfo.availability` 字段。`official` 表示目录身份;`availability` 为 `installation`、`profile` 或 `missing`。`installed` 仍表示 profile 的依赖声明。可选的 `installTarget` 给出 Host 选择的精确 spec 和版本。Client 详情插槽的 `PluginPackageRef` 也要求 `official` 和 `availability`。 +`pluginManager.listBundles()` 现在返回必需的 `BundleInfo.official` 和 `BundleInfo.availability` 字段。`official` 标识项目维护的随附可选组合包和按需目录项;`availability` 为 `installation`、`profile` 或 `missing`。`installed` 仍表示 profile 的依赖声明。可选的 `installTarget` 给出 Host 选择的精确 spec 和版本。Client 详情插槽的 `PluginPackageRef` 也要求 `official` 和 `availability`。 ## 迁移 @@ -21,5 +21,16 @@ description: "Claude Code 和 Codex bundle 为完整 Web 预设添加委派工 2. 移除针对原有休眠行 id 的配置补丁。使用作用于预设的 `preset` 补丁和相应 `optional-tool-subagent-*` id 配置新行。配置覆盖仍会替换整行的配置。 3. 对于 headless、SDK、ACP 或其他不包含这三个预设目标的配置,保留 `$DSH_HOME/profiles//package.json` 依赖中的提供方包,并从 `dsh.profile.bundles` 移除该条目。在该配置的 `cordis.patch.yml` 中显式挂载提供方和所需的通用委派工具;[Codex](../../../../packages/subagent/subagent-codex/README.zh.md#exposing-the-tool) 和 [Claude Code](../../../../packages/subagent/subagent-claude-code/README.zh.md#exposing-the-tool) 示例展示了相关行。这样可以保留仅安装提供方的工作流。 4. 使用目标预设打开新会话,确认工具目录包含所选委派工具。minimal 预设保持不变。关闭只取消选择 bundle 而不卸载;移除仍为独立操作。 - 5. 更新构造或校验 `BundleInfo` 或 `PluginPackageRef` 的客户端与测试数据,补充新的必需字段。使用 `official` 分组,使用 `availability` 判断包是否可用;仅凭 `installed: false` 不再能判断这是安装随附的组合包。 + + + +**恢复未包含完整预设的 profile** + +对已安装提供方包的 profile,在下次启动前运行以下命令;把 `headless` 换成所需 profile 名。该操作不启动 profile,只取消这两个 bundle 层的选择,保留包及其依赖版本: + +```sh +dsh plugin --profile headless exec node -e 'const fs=require("node:fs");const p="package.json";const m=JSON.parse(fs.readFileSync(p,"utf8"));m.dsh.profile.bundles=m.dsh.profile.bundles.filter(n=>!["@deepseek-ai/dsh-subagent-codex","@deepseek-ai/dsh-subagent-claude-code"].includes(n));fs.writeFileSync(p,JSON.stringify(m,null,2)+"\n");' +``` + +随后将所需提供方 README「暴露工具」中的 `insert` 操作加入该 profile 的 `cordis.patch.yml`,再运行 `dsh --profile headless --dump-config` 检查有效组合。共享 base 已提供作业服务,无需重复插入。 diff --git a/package.json b/package.json index 3b7a9e5720..d84b2dab40 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:bench": "npm run build:native-system && npm run build:lib && tsdown --config-loader native --config benchmarks/tsdown.config.ts", "build:official": "tsx scripts/build.ts --profile official", "build:lib": "pnpm run build:lib:host && pnpm run build:lib:client", - "build:lib:host": "pnpm run gen-official-bundle-catalog && node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --config-loader native --env.DSH_BUILD_FACE host && pnpm --filter @deepseek-ai/dsh-desktop run bundle", + "build:lib:host": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc -b tsconfig.host.json && tsdown --config-loader native --env.DSH_BUILD_FACE host && pnpm --filter @deepseek-ai/dsh-desktop run bundle", "build:lib:client": "tsc -b tsconfig.client.json && tsdown --config-loader native --env.DSH_BUILD_FACE client", "build:web": "pnpm --filter @deepseek-ai/dsh-web-frontend run build", "build:desktop": "pnpm --filter @deepseek-ai/dsh-desktop run build", diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index fbcf02fcbb..9c5831ff28 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -33,8 +33,8 @@ en: c4a301652b851ce9 zh: 784871e19cd9db0a /deepseek-ai-dsh-app-boot/use-this-package/reading-plugin-display-metadata: - en: e1ba2a7d4f6993ac - zh: 82df590cd2d037ba + en: 6ee530a717904067 + zh: e6b34f2d768094a2 /deepseek-ai-dsh-app-boot/use-this-package/startup-and-reload-failures: en: b2e5cfdf8847d214 zh: 8d65771a3bd8a7c9 diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index 68979952ff..f5a95ac0c1 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -102,7 +102,7 @@ The projector preserves native omission behavior by checking literal defaults ag 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 `/icon` when that field is omitted; a subpath uses `/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; build-time generation checks resource completeness and freshness without importing provider code. The catalog contains no version: management derives the target from `getDshRuntimeVersion()` 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. +`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 version: management derives the target from `getDshRuntimeVersion()` 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. ### Startup and reload failures diff --git a/packages/boot/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md index 1870a205d2..e19b90ebcc 100644 --- a/packages/boot/app-boot/README.zh.md +++ b/packages/boot/app-boot/README.zh.md @@ -102,7 +102,7 @@ profile patch 可以将 `preset` 设为外层条目 id,把其余普通 patch 使用 `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` 内嵌这些包拥有的本地化元信息和图标,支持离线发现;构建时生成器检查资源完整性与新鲜度,不导入提供者代码。目录不保存版本;管理器通过 `getDshRuntimeVersion()` 确定目标版本,并使用普通组合包安装器。[产品用途检查](../../../scripts/verify-product-use.ts)将随附和按需选择与 Web 组合,但不执行提供者。 +`ON_DEMAND_BUNDLES` 列出仅按需安装的官方公开包。`OFFICIAL_ON_DEMAND_CATALOG` 内嵌这些包拥有的本地化元信息和图标,支持离线发现;`pnpm gen-official-bundle-catalog` 显式更新生成内容,`verify-official-bundle-catalog` 检查资源完整性与新鲜度;构建只读取已提交的目录,不导入提供者代码。目录不保存版本;管理器通过 `getDshRuntimeVersion()` 确定目标版本,并使用普通组合包安装器。[产品用途检查](../../../scripts/verify-product-use.ts)将随附和按需选择与 Web 组合,但不执行提供者。 ### 启动与重载失败 diff --git a/packages/boot/plugin-manager/README.i18n.yaml b/packages/boot/plugin-manager/README.i18n.yaml index 3202076c88..4347d8e017 100644 --- a/packages/boot/plugin-manager/README.i18n.yaml +++ b/packages/boot/plugin-manager/README.i18n.yaml @@ -15,8 +15,8 @@ en: 87679c51aa3693f5 zh: 11684a48a5806afb /deepseek-ai-dsh-plugin-manager/use-this-package: - en: 67f47e047834270d - zh: 9d9e1bdb92939532 + en: c45231f0dcdd7a8c + zh: 230e512821b849a4 /deepseek-ai-dsh-plugin-manager/use-this-package/version-compatibility-and-exemptions: en: 920c0acb3b9feaf3 zh: ba6e832a256880a7 diff --git a/packages/boot/plugin-manager/README.md b/packages/boot/plugin-manager/README.md index 18218e7ca5..f6c28c41f8 100644 --- a/packages/boot/plugin-manager/README.md +++ b/packages/boot/plugin-manager/README.md @@ -45,7 +45,7 @@ Scoped bundle rows carry both their declared `rowId` and outer `preset` target. `listBundles` includes optional display `meta` for each bundle and its declared plugin rows, including disabled bundles. The Client selects a language from these values. The separate `description` field is the bundle's raw `package.json.description`; a metadata diagnostic does not block management. `source` gives a profile dependency as a spec `pnpm add` accepts: a git address or URL as recorded without the user information of an http(s) URL (a query string stays as recorded), a `file:` or `link:` path made absolute against the profile or the home directory for `~`, and a registry range, tag, or alias after the package name, as is any spec of a package installed under another name; a bundle the installation supplies has none, even when the profile also depends on it, because the installation's copy is the one that loads. The `plugin_manager` tool omits UI display metadata from list results. -`listBundles` keeps `official` catalog membership independent of package installation. `availability` identifies a readable installation-provided or profile package, or `missing`; `installed` only records whether the profile declares the dependency. An absent on-demand entry carries embedded localized metadata and a host-derived `installTarget` containing the exact running DSH version and install spec, without package resolution or registry access. Profile-declared packages with unreadable files retain their declaration and expose the normal error. +`listBundles` marks shipped optional bundles and on-demand catalog entries as `official`, independently of package installation. `availability` identifies a readable installation-provided or profile package, or `missing`; on-demand catalog entries exclude undeclared transitive copies. `installed` only records whether the profile declares the dependency. An absent on-demand entry carries embedded localized metadata and a host-derived `installTarget` containing the exact running DSH version and install spec, without package resolution or registry access. Profile-declared packages with unreadable files retain their declaration and expose the normal error. `inspect(spec, options)` reads what a spec names before anything installs: a registry name is asked of the registry through `pnpm view`, run in the profile directory so the same proxy and authentication settings apply as to the install; an absolute path has its `package.json` read; a git address or tarball answers only its form and the `host` it is fetched from. The answer carries the name, version, description, whether the package declares a bundle, and the `registry` that answered, or a `problem`: `invalid-spec`, `already-installed`, `not-found`, `not-a-package`, `not-a-bundle`, `network`, or `unknown`, with the `registries` asked. A caller's `signal` or `inspectTimeoutMs` ends the lookup. diff --git a/packages/boot/plugin-manager/README.zh.md b/packages/boot/plugin-manager/README.zh.md index 81c5bdb818..5acc7dccba 100644 --- a/packages/boot/plugin-manager/README.zh.md +++ b/packages/boot/plugin-manager/README.zh.md @@ -45,7 +45,7 @@ kind: "package-reference" `listBundles` 为各组合包及其声明的插件行提供可选的展示 `meta`,包括已禁用的组合包。Client 从这些值中选择语言。单独的 `description` 字段是该组合包原始的 `package.json.description`;元信息诊断不会阻止管理操作。`source` 以 `pnpm add` 接受的 spec 给出 profile 依赖:git 地址或 URL 按记录给出,但去掉 http(s) URL 的 user information(查询字符串保持原样);`file:` 或 `link:` 路径按 profile 目录解析为绝对路径,`~` 展开为主目录;注册表版本范围、标签或别名接在包名的 `@` 之后,以别名安装的包的 spec 也是如此。安装提供的组合包没有该字段,即使 profile 也依赖它,因为实际加载的是安装中的副本。`plugin_manager` 工具的列表结果不包含 UI 展示元信息。 -`listBundles` 的 `official` 目录身份与包的安装状态独立。`availability` 表示可读取的安装随附包、profile 包,或 `missing`;`installed` 仅表示 profile 是否声明该依赖。尚未安装的按需条目携带内嵌的本地化元信息,以及 Host 根据当前 DSH 精确版本生成、包含版本与安装 spec 的 `installTarget`,无需解析包或访问注册表。profile 已声明但文件不可读的包保留依赖声明,并展示原有错误。 +`listBundles` 将随附可选组合包和按需目录项标为 `official`,与包的安装状态独立。`availability` 表示可读取的安装随附包、profile 包,或 `missing`;按需目录项不计入未声明的传递依赖副本。`installed` 仅表示 profile 是否声明该依赖。尚未安装的按需条目携带内嵌的本地化元信息,以及 Host 根据当前 DSH 精确版本生成、包含版本与安装 spec 的 `installTarget`,无需解析包或访问注册表。profile 已声明但文件不可读的包保留依赖声明,并展示原有错误。 `inspect(spec, options)` 在任何东西安装之前读出 spec 指向什么:注册表包名通过 `pnpm view` 询问注册表,在 profile 目录中运行,因而与安装使用同样的代理与认证设置;绝对路径读取其 `package.json`;git 地址或 tarball 只答复自己的形式和它被拉取的 `host`。答复携带名称、版本、描述、该包是否声明组合包,以及作答的 `registry`,否则给出 `problem`:`invalid-spec`、`already-installed`、`not-found`、`not-a-package`、`not-a-bundle`、`network` 或 `unknown`,并附上问过的 `registries`。调用方的 `signal` 或 `inspectTimeoutMs` 会结束查询。 diff --git a/packages/boot/plugin-manager/src/tools.ts b/packages/boot/plugin-manager/src/tools.ts index df9b0a200e..29c58e898d 100644 --- a/packages/boot/plugin-manager/src/tools.ts +++ b/packages/boot/plugin-manager/src/tools.ts @@ -21,7 +21,7 @@ export function apply(ctx: Context): void { description: 'List plugins or bundles in the current profile, enable or disable them, install a bundle, or remove an installed bundle. Every action requires danger-full-access permission or approval for this call. Approval does not change the session permission mode. Changes affect every session in this profile. List first to obtain exact identifiers. Package installation can execute allowed build scripts. Live profiles apply changes immediately; startup profiles require restart. Incompatible DSH peer dependencies block installation and activation. Version exemptions risk crashes and data loss: warn the user and obtain explicit permission for the exact plugin and runtime versions before granting one.', parameters: { action: { type: 'string', required: true, enum: ['list_plugins', 'list_bundles', 'set_plugin', 'set_bundle', 'install_bundle', 'remove_bundle', 'list_version_exemptions', 'set_version_exemption'], description: 'Management operation.' }, - target: { type: 'string', description: 'Plugin entry id, bundle package name, or installation spec, according to action.' }, + target: { type: 'string', description: 'Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle.' }, enabled: { type: 'boolean', description: 'Required for set operations; defaults to true for installation. For set_version_exemption, true grants and false revokes.' }, runtimeVersion: { type: 'string', description: 'For set_version_exemption: exact DSH version from list_version_exemptions. Target must be the manifest package-name@version, not an alias or version range.' }, acceptRisk: { type: 'boolean', description: 'For granting an exemption: true only after warning the user about possible crashes and data loss and receiving explicit permission for this exact plugin/runtime pair. General installation permission is not enough.' }, diff --git a/packages/boot/plugin-manager/src/types.ts b/packages/boot/plugin-manager/src/types.ts index d690003486..59ea50a154 100644 --- a/packages/boot/plugin-manager/src/types.ts +++ b/packages/boot/plugin-manager/src/types.ts @@ -49,12 +49,12 @@ export interface BundleRowInfo { entryId?: PluginEntryId } -/** One installed or installation-provided bundle. */ +/** One shipped, profile-installed, or discoverable on-demand bundle. */ export interface BundleInfo { name: string - /** Listed in the installation's Official catalog, independent of installation and selection. */ + /** Project-maintained shipped optional bundle or on-demand catalog entry, independent of installation and selection. */ official: boolean - /** Where a readable package is available; a missing package can still have a profile dependency declaration. */ + /** Readable package location; on-demand catalog entries exclude undeclared transitive copies, and declared files may be missing. */ availability: 'installation' | 'profile' | 'missing' /** Exact host-version target offered for an on-demand catalog entry. */ installTarget?: { spec: string; version: string } diff --git a/packages/boot/plugin-manager/tests/tools.spec.ts b/packages/boot/plugin-manager/tests/tools.spec.ts index e18b5acce8..bc7da82dfe 100644 --- a/packages/boot/plugin-manager/tests/tools.spec.ts +++ b/packages/boot/plugin-manager/tests/tools.spec.ts @@ -10,6 +10,7 @@ import SessionProjections from '@deepseek-ai/dsh-session-projection' import ApprovalService, { type ApprovalOutcome } from '@deepseek-ai/dsh-user-approval' import { expect, it, onTestFinished, vi } from 'vitest' import type PluginManager from '../src/index.ts' +import type { BundleInfo } from '../src/types.ts' import * as tool from '../src/tools.ts' function resultText(result: Awaited>): string { @@ -80,6 +81,40 @@ it('preserves selected bundle load errors in the agent list result', async () => `) }) +it('exposes an absent Official bundle installation target without display metadata', async () => { + const { manager, call } = await fixture() + const offered: BundleInfo = { + name: '@deepseek-ai/dsh-subagent-codex', official: true, availability: 'missing', + installed: false, enabled: false, optional: false, removable: false, rows: [], overrides: [], + installTarget: { spec: '@deepseek-ai/dsh-subagent-codex@1.2.3', version: '1.2.3' }, + meta: { title: 'Codex subagent' }, + } + manager.listBundles.mockResolvedValue([offered]) + expect(JSON.parse(resultText(await call({ action: 'list_bundles' })))).toMatchInlineSnapshot(` + { + "entries": [ + { + "availability": "missing", + "enabled": false, + "installTarget": { + "spec": "@deepseek-ai/dsh-subagent-codex@1.2.3", + "version": "1.2.3", + }, + "installed": false, + "name": "@deepseek-ai/dsh-subagent-codex", + "official": true, + "optional": false, + "overrides": [], + "removable": false, + "rows": [], + }, + ], + "nextOffset": null, + "total": 1, + } + `) +}) + it('checks the calling session on each execution, including after permission is revoked', async () => { const { call, manager } = await fixture() const id = SessionId('manager-permissions') diff --git a/packages/client/ui-plugin-manager/README.i18n.yaml b/packages/client/ui-plugin-manager/README.i18n.yaml index 27bdf61776..1ee525948b 100644 --- a/packages/client/ui-plugin-manager/README.i18n.yaml +++ b/packages/client/ui-plugin-manager/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-client-ui-plugin-manager/use-this-package: - en: f72a9368846d5320 - zh: de826dcd7dce5c1d + en: 19302cba5e104b0a + zh: d9e1dc6ec9d2705b /deepseek-ai-dsh-client-ui-plugin-manager/use-this-package/installing-a-bundle: en: 36acfd5776cfae9b zh: be23d0a20c051dc5 diff --git a/packages/client/ui-plugin-manager/README.md b/packages/client/ui-plugin-manager/README.md index b72a47f5c6..17717446eb 100644 --- a/packages/client/ui-plugin-manager/README.md +++ b/packages/client/ui-plugin-manager/README.md @@ -31,7 +31,7 @@ Select **Plugins** in the sidebar. The information button uses an 11px caption-c Installed bundles and their plugin rows display their own title and description in the current UI language on cards and detail pages. Each field falls back from exported locale `meta` to the accessible `package.json` of a package-root plugin; subpath plugins never read one; the final title is the full package or module name, with no package description when both sources omit it. See [Plugin display metadata](../../../docs/cookbook/adding-a-package.md#plugin-display-metadata) for the author format. Bundle cards, details, and component rows display the package root's manifest `icon` or exported `./icon`, or a subpath plugin's exported `/icon`; absent or undecodable images retain the default artwork. Installation previews still use registry or manifest information. -On-demand Official cards are visible offline before installation. Turning one On opens the existing install progress dialog, installs the exact version offered by the running DSH host, and selects its layer. The same registry fallback, cancellation, and script-approval controls apply. Turning Off keeps the dependency installed; Remove is a separate action. When an installed catalog package differs from the host target version, its detail page offers **Update** and displays both versions. Update preserves selection and reports any required restart. A package with missing or damaged files remains distinguishable from an installation-carried package. +On-demand Official cards are visible offline before installation. Turning one On opens the existing install progress dialog, installs the exact version offered by the running DSH host, and selects its layer. The same registry fallback, cancellation, and script-approval controls apply. Turning Off keeps the dependency installed; Remove is a separate action. When an installed catalog package differs from the host target version, its detail page offers **Update** and displays both versions. Update preserves selection and reports any required restart. A package with missing or damaged files remains distinguishable from an installation-carried package; its detail page keeps the recorded dependency source visible beside the unavailable status. The first list read shows a short Official-heading placeholder and four skeleton cards, using the real cards' row, icon, and text-line spacing, with the action area left blank. They use the same opacity pulse as Session search and remain static with reduced motion. The accessible loading label and read timing are unchanged; refreshes retain the existing list, and installation and component-loading indicators keep their own states. diff --git a/packages/client/ui-plugin-manager/README.zh.md b/packages/client/ui-plugin-manager/README.zh.md index dcfd02a5fa..dbdcfb3b45 100644 --- a/packages/client/ui-plugin-manager/README.zh.md +++ b/packages/client/ui-plugin-manager/README.zh.md @@ -31,7 +31,7 @@ kind: "package-reference" 已安装的组合包及其插件行在卡片和详情页中,按当前界面语言显示各自的标题与描述。每个字段先读取导出的 locale `meta`,缺失时回退到包根插件可访问的 `package.json`,子路径插件从不读取;标题最终使用完整包名或模块名,两处都没有描述时不提供包描述。作者格式见[插件展示元信息](../../../docs/cookbook/adding-a-package.zh.md#plugin-display-metadata)。组合包卡片、详情和组件行显示包根的清单 `icon` 或导出的 `./icon`,或子路径插件导出的 `<子路径>/icon`;未声明或无法解码时保留默认插画。安装预览仍使用注册表或 manifest 信息。 -官方按需卡片在安装前即可离线显示。开启会打开已有的安装进度对话框,安装当前 DSH Host 提供的精确版本并选择配置层。安装源回退、取消和脚本审批继续使用同一组控件。关闭保留依赖;移除是独立操作。已安装目录包的版本与 Host 目标版本不同时,详情页提供**更新**并显示两个版本。更新保留选择状态,并报告是否需要重启。文件缺失或损坏的包与安装随附的包保持明确区分。 +官方按需卡片在安装前即可离线显示。开启会打开已有的安装进度对话框,安装当前 DSH Host 提供的精确版本并选择配置层。安装源回退、取消和脚本审批继续使用同一组控件。关闭保留依赖;移除是独立操作。已安装目录包的版本与 Host 目标版本不同时,详情页提供**更新**并显示两个版本。更新保留选择状态,并报告是否需要重启。文件缺失或损坏的包与安装随附的包保持明确区分;详情页在显示不可用状态的同时,保留记录的依赖来源。 首次读取列表时,页面显示一条较短的「官方」标题占位和四行骨架卡片,沿用真实卡片的行、图标和文字间距,操作区域留白。骨架使用与 Session 搜索相同的透明度呼吸动效,在减少动态效果模式下保持静止。无障碍加载文案和读取时机不变;刷新时保留已有列表,安装及组件加载仍使用各自的状态提示。 diff --git a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx index 2f7912e890..aafd87c7ec 100644 --- a/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx +++ b/packages/client/ui-plugin-manager/src/client/PluginManagerPage.tsx @@ -310,12 +310,7 @@ function RowsSection({ rows, t, resolveText, toggle, configure }: { ) } -/** - * Where a bundle comes from: the spec that installs it elsewhere, or built in - * for one whose loaded copy the installation supplies, and its version. A - * selected bundle that neither the profile nor the installation holds has no - * section. - */ +/** Declared install source and versions; unavailable files retain their recorded dependency spec. */ function SourceSection({ pkg, t }: { readonly pkg: PackageView; readonly t: Translate }): ReactNode { if (pkg.source === undefined && !pkg.installed && pkg.availability === 'installation' && !pkg.optional) return null return ( @@ -324,7 +319,8 @@ function SourceSection({ pkg, t }: { readonly pkg: PackageView; readonly t: Tran
{t('sourceSpec')}
-
{pkg.availability === 'missing' ? t(pkg.installed ? 'statusMissingFiles' : 'sourceNotInstalled') : pkg.source === undefined ? t('sourceBuiltIn') : {pkg.source}}
+
{pkg.source !== undefined ? {pkg.source} : pkg.availability === 'missing' + ? t(pkg.installed ? 'statusMissingFiles' : 'sourceNotInstalled') : t('sourceBuiltIn')}
{pkg.version === undefined ? null diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts index e53c11b193..20e250cd6c 100644 --- a/packages/client/ui-plugin-manager/src/client/locales.ts +++ b/packages/client/ui-plugin-manager/src/client/locales.ts @@ -76,7 +76,7 @@ export const zh = { installGuideFill: '填入示例', installGuideFillAria: '填入示例 {example}', installGuideSafety: '请确认插件来源可信。插件在本机以你的权限运行,来源不明的插件可能损坏 DeepSeek Harness,或读取和泄露你的数据。', - installUpgradeNotice: '按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版', + installUpgradeNotice: '按需安装的官方插件会在版本与 DSH 不同时提供“更新”。其他插件请先卸载再安装新版。', registryToggle: '安装源', registryLegend: '从哪个 npm 源下载插件', registryDefault: '默认安装源', diff --git a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx index d663389d0f..f3dc956f68 100644 --- a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx +++ b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx @@ -2089,12 +2089,14 @@ describe('on-demand Official bundles', () => { expect(actions.update).toHaveBeenCalledWith(name) }) - it('distinguishes unreadable installed files and offers the existing update action to repair them', () => { - renderTab({ packages: [{ ...catalog(), installed: true, removable: true, error: { code: 'operation-error', diagnostic: 'Unreadable package files' } }] }) + it.each([undefined, `${name}@1.0.0`])('retains dependency source=%s beside unavailable status and the update action', (source) => { + renderTab({ packages: [{ ...catalog(), ...source === undefined ? {} : { source }, installed: true, removable: true, error: { code: 'operation-error', diagnostic: 'Unreadable package files' } }] }) expect(screen.getByText(en.statusMissingFiles)).toBeTruthy() fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) })) const detail = document.querySelector('[data-plugin-detail]') as HTMLElement expect(within(detail).queryByText(en.statusNotInstalled)).toBeNull() + if (source !== undefined) expect(within(detail).getByText(source).tagName).toBe('CODE') + expect(within(detail).queryAllByText(en.statusMissingFiles)).not.toEqual([]) expect(within(detail).getByRole('button', { name: en.updateLabel.replace('{name}', title) })).toBeTruthy() }) diff --git a/packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts b/packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts index d3e3dc2b20..659eed06e6 100644 --- a/packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts +++ b/packages/client/ui-plugin-manager/tests/manager-store.client.spec.ts @@ -1834,6 +1834,38 @@ it('installs an absent Official entry directly at the host target and enables it expect(state().install.subject).toMatchObject({ selection: true, saveExact: true, version: '2.0.0' }) }) +it.each(['before', 'after'] as const)('reports one Official toggle when inventory refresh finishes %s installation', async (order) => { + const installing = deferred>>() + const refreshing = deferred>>() + const installed: BundleInfo = { ...CATALOG, availability: 'profile', installed: true, enabled: true, removable: true, version: '2.0.0' } + const result = ok({ ...APPLIED, bundle: CATALOG.name }) + const listBundles = vi.fn().mockResolvedValue(ok([installed])) + .mockResolvedValueOnce(ok([CATALOG])).mockReturnValueOnce(refreshing.promise) + const { controller, face, state, started, track } = bench({ listBundles, installBundle: vi.fn(() => installing.promise) }) + const reads: Promise[] = [] + onTestFinished(async () => { installing.resolve(result); refreshing.resolve(ok([installed])); await Promise.all(reads) }) + await controller.load() + face.setEnabled(CATALOG.name, true) + await started() + const refresh = controller.load() + reads.push(refresh) + await vi.waitFor(() => { expect(listBundles).toHaveBeenCalledTimes(2) }) + expect(state().packages.find(pkg => pkg.name === CATALOG.name)?.availability).toBe('missing') + if (order === 'before') { + refreshing.resolve(ok([installed])) + await refresh + } + installing.resolve(result) + await vi.waitFor(() => { expect(state().install.phase).toBe('done') }) + expect(track.mock.calls.filter(([event]) => event === 'plugin_toggle')).toEqual([['plugin_toggle', { + plugin_name: CATALOG.name, plugin_type: 'bundle', is_enabled: true, is_builtin: false, + }]]) + refreshing.resolve(ok([installed])) + await refresh + await controller.load() + expect(track.mock.calls.filter(([event]) => event === 'plugin_toggle')).toHaveLength(1) +}) + it.each([true, false])('updates an Official entry with enabled=%s through the same exact installer', async (enabled) => { const installed: BundleInfo = { ...CATALOG, availability: 'profile', installed: true, version: '1.0.0', enabled, removable: true } const { controller, plugins, face, state } = bench({ listBundles: vi.fn().mockResolvedValue(ok([installed])), diff --git a/packages/subagent/subagent-claude-code/README.md b/packages/subagent/subagent-claude-code/README.md index 77d12ebcd3..2af38cc2d6 100644 --- a/packages/subagent/subagent-claude-code/README.md +++ b/packages/subagent/subagent-claude-code/README.md @@ -58,17 +58,18 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### Exposing the tool -The bundle adds `optional-tool-subagent-claude-code` to each full preset through scoped profile patches. The minimal preset and host tool catalog remain unchanged. For a custom profile without these preset targets, keep the package dependency and compose the provider and tool directly: +The bundle adds `optional-tool-subagent-claude-code` to each full preset through scoped profile patches. The minimal preset and host tool catalog remain unchanged. For a profile without these preset targets that uses the shared base services, follow the [recovery steps](../../../docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md#recovery) to keep the dependency and deselect its bundle layer, then add this insertion to the profile's `cordis.patch.yml`: ```yaml -- id: subagent-claude-code - name: '@deepseek-ai/dsh-subagent-claude-code' -- id: tool-subagent-claude - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-code - toolName: subagent_claude_code - maxDepth: provider-managed +- insert: + - id: subagent-claude-code + name: '@deepseek-ai/dsh-subagent-claude-code' + - id: tool-subagent-claude + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-code + toolName: subagent_claude_code + maxDepth: provider-managed ``` The tool returns a child id after accepting the task and sends its result to the parent Agent on completion. Each external activation executes once and accepts no follow-up input or resume. diff --git a/packages/subagent/subagent-claude-code/README.zh.md b/packages/subagent/subagent-claude-code/README.zh.md index 8e46c7ec0c..4457107cf6 100644 --- a/packages/subagent/subagent-claude-code/README.zh.md +++ b/packages/subagent/subagent-claude-code/README.zh.md @@ -58,17 +58,18 @@ kind: "package-bundle" ### 暴露工具 -Bundle 通过作用于预设的配置补丁为每个完整预设添加 `optional-tool-subagent-claude-code`。minimal 预设和 Host 工具目录保持不变。对于不包含这些预设目标的自定义配置,保留包依赖并直接组合提供方和工具: +Bundle 通过作用于预设的配置补丁为每个完整预设添加 `optional-tool-subagent-claude-code`。minimal 预设和 Host 工具目录保持不变。对于不包含这些预设目标、但使用共享 base 服务的 profile,先按[恢复步骤](../../../docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md#recovery)保留依赖并取消选择 bundle 层,再把以下插入操作加入该 profile 的 `cordis.patch.yml`: ```yaml -- id: subagent-claude-code - name: '@deepseek-ai/dsh-subagent-claude-code' -- id: tool-subagent-claude - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: claude-code - toolName: subagent_claude_code - maxDepth: provider-managed +- insert: + - id: subagent-claude-code + name: '@deepseek-ai/dsh-subagent-claude-code' + - id: tool-subagent-claude + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: claude-code + toolName: subagent_claude_code + maxDepth: provider-managed ``` 工具接受任务后返回 child id;任务完成后向父 agent 发送结果通知。外部 activation 只执行一次,不支持追加输入或恢复对话。 diff --git a/packages/subagent/subagent-codex/README.md b/packages/subagent/subagent-codex/README.md index 71a1aaba27..969336a525 100644 --- a/packages/subagent/subagent-codex/README.md +++ b/packages/subagent/subagent-codex/README.md @@ -56,17 +56,18 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a ### Exposing the tool -The bundle adds `optional-tool-subagent-codex` to each full preset through scoped profile patches. The minimal preset and host tool catalog remain unchanged. For a custom profile without these preset targets, keep the package dependency and compose the provider and tool directly: +The bundle adds `optional-tool-subagent-codex` to each full preset through scoped profile patches. The minimal preset and host tool catalog remain unchanged. For a profile without these preset targets that uses the shared base services, follow the [recovery steps](../../../docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.md#recovery) to keep the dependency and deselect its bundle layer, then add this insertion to the profile's `cordis.patch.yml`: ```yaml -- id: subagent-codex - name: '@deepseek-ai/dsh-subagent-codex' -- id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex - toolName: subagent_codex - maxDepth: provider-managed +- insert: + - id: subagent-codex + name: '@deepseek-ai/dsh-subagent-codex' + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex + toolName: subagent_codex + maxDepth: provider-managed ``` The tool returns a child id after accepting the task and sends its result to the parent Agent on completion. Each external activation executes once and accepts no follow-up input or resume. diff --git a/packages/subagent/subagent-codex/README.zh.md b/packages/subagent/subagent-codex/README.zh.md index 403839a1f9..b2f6e414f6 100644 --- a/packages/subagent/subagent-codex/README.zh.md +++ b/packages/subagent/subagent-codex/README.zh.md @@ -56,17 +56,18 @@ kind: "package-bundle" ### 暴露工具 -Bundle 通过作用于预设的配置补丁为每个完整预设添加 `optional-tool-subagent-codex`。minimal 预设和 Host 工具目录保持不变。对于不包含这些预设目标的自定义配置,保留包依赖并直接组合提供方和工具: +Bundle 通过作用于预设的配置补丁为每个完整预设添加 `optional-tool-subagent-codex`。minimal 预设和 Host 工具目录保持不变。对于不包含这些预设目标、但使用共享 base 服务的 profile,先按[恢复步骤](../../../docs/upgrade-guide/v0.2.1-alpha.1/native-subagent-bundle-tools/guide.zh.md#recovery)保留依赖并取消选择 bundle 层,再把以下插入操作加入该 profile 的 `cordis.patch.yml`: ```yaml -- id: subagent-codex - name: '@deepseek-ai/dsh-subagent-codex' -- id: tool-subagent-codex - name: '@deepseek-ai/dsh-tool-subagent' - config: - provider: codex - toolName: subagent_codex - maxDepth: provider-managed +- insert: + - id: subagent-codex + name: '@deepseek-ai/dsh-subagent-codex' + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: codex + toolName: subagent_codex + maxDepth: provider-managed ``` 工具接受任务后返回 child id;任务完成后向父 agent 发送结果通知。外部 activation 只执行一次,不支持追加输入或恢复对话。 diff --git a/scripts/gen-official-bundle-catalog.spec.ts b/scripts/gen-official-bundle-catalog.spec.ts index 623c5d18ae..54da13d7c3 100644 --- a/scripts/gen-official-bundle-catalog.spec.ts +++ b/scripts/gen-official-bundle-catalog.spec.ts @@ -1,6 +1,6 @@ /** Offline Official catalog ownership, completeness, and freshness checks. */ -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' @@ -104,4 +104,23 @@ describe('Official bundle catalog generation', () => { syncOfficialBundleCatalog(root, false) expect(readFileSync(join(root, output), 'utf8')).toContain('Changed') }) + + it('does not rewrite an already current catalog during explicit generation', () => { + const root = fixture() + syncOfficialBundleCatalog(root, false) + const path = join(root, output) + const timestamp = new Date('2000-01-01T00:00:00Z') + utimesSync(path, timestamp, timestamp) + const before = statSync(path).mtimeMs + syncOfficialBundleCatalog(root, false) + expect(statSync(path).mtimeMs).toBe(before) + expect(() => { syncOfficialBundleCatalog(root, true) }).not.toThrow() + }) + + it('rejects a missing generated catalog without recreating it during freshness checking', () => { + const root = fixture() + rmSync(join(root, output)) + expect(() => { syncOfficialBundleCatalog(root, true) }).toThrow('is stale') + expect(existsSync(join(root, output))).toBe(false) + }) }) diff --git a/scripts/gen-official-bundle-catalog.ts b/scripts/gen-official-bundle-catalog.ts index 3db88369cb..25e08f48c6 100644 --- a/scripts/gen-official-bundle-catalog.ts +++ b/scripts/gen-official-bundle-catalog.ts @@ -106,16 +106,19 @@ function localized(value: LocalizedText): string { } /** - * Write or freshness-check metadata against the owning package resources. + * Write changed metadata or freshness-check it against the owning package resources. * @param root - Repository root containing source and metadata resources. * @param check - Reject drift without changing the generated file. */ export function syncOfficialBundleCatalog(root: string, check: boolean): void { const source = renderOfficialBundleCatalog(generateOfficialBundleCatalog(root)) const path = resolve(root, OUTPUT) - if (check) { - if (readFileSync(path, 'utf8') !== source) throw new Error(`${OUTPUT} is stale; run pnpm gen-official-bundle-catalog`) - } else writeFileSync(path, source) + let current: string | undefined + try { current = readFileSync(path, 'utf8') } + catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error } + if (current === source) return + if (check) throw new Error(`${OUTPUT} is stale; run pnpm gen-official-bundle-catalog`) + writeFileSync(path, source) } if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { diff --git a/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json b/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json index 4427c8711e..f44f52640a 100644 --- a/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json +++ b/snapshots/session/plugin-manager-mcp/tool-schemas.expected.json @@ -282,7 +282,7 @@ }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", @@ -1025,7 +1025,7 @@ }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean", diff --git a/snapshots/session/plugin-manager/tool-schemas.expected.json b/snapshots/session/plugin-manager/tool-schemas.expected.json index 774cddaa2b..4596dae7a1 100644 --- a/snapshots/session/plugin-manager/tool-schemas.expected.json +++ b/snapshots/session/plugin-manager/tool-schemas.expected.json @@ -282,7 +282,7 @@ }, "target": { "type": "string", - "description": "Plugin entry id, bundle package name, or installation spec, according to action." + "description": "Plugin entry id, bundle package name, or installation spec, according to action. For an Official catalog entry, pass list_bundles installTarget.spec to install_bundle." }, "enabled": { "type": "boolean",