diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml
index a857ff2101..209838a431 100644
--- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.i18n.yaml
@@ -9,8 +9,8 @@
en: 9ca845902f1df3a2
zh: 41e3157a843a511e
/agent-note-plugin-configuration-on-the-plugins-page/decision:
- en: edce6321f96653c1
- zh: b9af8efeff5a05ed
+ en: ccee8068ea810fcb
+ zh: c87c4e6c44b33a67
/agent-note-plugin-configuration-on-the-plugins-page/consequences:
en: d3d131d6c46bc32e
zh: eb7c06f119dfbe8d
diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md
index 700ada1538..35aaefd028 100644
--- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md
+++ b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.md
@@ -20,7 +20,7 @@ The page passes `view: 'page'` for each form. Official plugin cards and details
**The four host-plane pages register while the Host serves their namespaces.** Each page's package registers it through `ctx.slots.inject` when the shared settings mirror shows the namespace and disposes it when the namespace goes, so a deployment that does not compose the owning plugin shows no trace of it; since [settings pages as companion packages](2026-09-17-settings-pages-as-companion-packages.md) that is one companion package per page, and `ui-settings-plugins` keeps the Settings section as the **Built-in plugins** shell around the inventory tab. The `settings.plugin.item` slot is retired.
-**The Official group.** The optional bundles the installation ships open the group, tagged **Beta** where the feature is one (Agent Teams), with no official tag; the configuration pages follow. Auto review is a published experimental package used as the install guide's example, not an `OPTIONAL_BUNDLES` entry or a CLI dependency.
+**The Official group.** The optional bundles the installation ships open the group, tagged **Beta** where the package is experimental, with no official tag; the configuration pages follow. The [optional-bundle decision for experimental capabilities](2026-09-21-experimental-capabilities-as-optional-bundles.md) names the entries.
## Consequences
diff --git a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md
index 23935312d2..ccdaf5629a 100644
--- a/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md
+++ b/.agents/notes/implemented/architecture/2026-09-16-plugin-configuration-on-the-plugins-page.zh.md
@@ -20,7 +20,7 @@ Status: implemented
**四个宿主平面页面在 Host 服务其命名空间期间注册。** 每个页面所在的包在共享的 settings 镜像显示某个命名空间时通过 `ctx.slots.inject` 注册对应页面,命名空间消失时销毁,因此没有组装该插件的部署不会留下它的痕迹;自[设置页作为伴生包](2026-09-17-settings-pages-as-companion-packages.zh.md)起,每个页面各是一个伴生包,`ui-settings-plugins` 把设置分区保留为清单标签页外面的**内置插件**外壳。`settings.plugin.item` slot 退役。
-**官方分组。** 安装随附的可选组合包开启这个分组,属于 beta 功能的(Agent Teams)带 **Beta** 标签,没有官方标签;配置页排在其后。Auto review 是安装引导用作示例的已发布实验包,不属于 `OPTIONAL_BUNDLES` 或 CLI 依赖。
+**官方分组。** 安装随附的可选组合包开启这个分组,属于实验包的带 **Beta** 标签,没有官方标签;配置页排在其后。[实验能力的可选 bundle 决策](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)列出这些条目。
## 后果
diff --git a/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml
new file mode 100644
index 0000000000..e8573b2e72
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.i18n.yaml
@@ -0,0 +1,19 @@
+# Bilingual-pair consistency record for 2026-09-21-experimental-capabilities-as-optional-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-09-21-experimental-capabilities-as-optional-bundles.md
+/agent-note-ship-experimental-capabilities-as-optional-bundles:
+ en: 9ec3d64c70d601b6
+ zh: c85722cfc7bf70df
+/agent-note-ship-experimental-capabilities-as-optional-bundles/problem:
+ en: 2ba6c43b20a3d347
+ zh: 5647179ddaa38ad9
+/agent-note-ship-experimental-capabilities-as-optional-bundles/decision:
+ en: 1c5a43b3ce2d3056
+ zh: b59f549a12cf67d1
+/agent-note-ship-experimental-capabilities-as-optional-bundles/alternatives-considered:
+ en: a65d84004e48b3ef
+ zh: 17221f08e7a8a89b
+/agent-note-ship-experimental-capabilities-as-optional-bundles/consequences:
+ en: d52ed0a248131a37
+ zh: 779b8d415281c5da
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
new file mode 100644
index 0000000000..a2a83645c9
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md
@@ -0,0 +1,25 @@
+# Agent Note: Ship experimental capabilities as optional bundles
+
+Status: implemented
+
+English | [中文](2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)
+
+## Problem
+
+The Web plugin page offered two optional bundles, Agent Teams and voice input. Auto review and the Inspector were published experimental packages that a person had to install by name or mount through a hand-written profile patch, although both already declared a bundle patch or shipped a mountable overlay.
+
+## Decision
+
+`OPTIONAL_BUNDLES` lists Agent Teams, voice input, and Auto review. Every optional bundle declares `icon` and exports `./locale/*.json` with `meta.title` and `meta.description`, so the Official group renders a localized title, description, and image; `verify-default-product-isolation` rejects an optional bundle without them. The Inspector's `cordis.patch.yml` names the package instead of a built-artifact path, so the same file is the bundle patch and the `--patch` overlay for a built Web launch.
+
+An optional bundle is a runtime dependency of the installation, so its dependency graph is downloaded by every `dsh` install. The list therefore admits only packages whose graph is already in the installation's closure; Auto review adds nothing. The Inspector stays available through explicit installation and does not appear in the default plugin list. The browser-use and computer-use providers stay explicit compositions: Playwright MCP, Chrome DevTools MCP, and the native Cua Driver runtime binaries add about 85 MB and 21 packages to every install whether or not the bundle is enabled, and a shipped Cua Driver MCP switch would offer a capability whose executable the installation does not carry. Those provider packages keep locale display metadata for their component rows. Two more packages stay out for other reasons: `ptc-runtime-python` replaces the PTC runtime, and `workflow-ptc` rejects a non-TypeScript runtime at load while the Web presets carry `workflow-ptc` rows a bundle patch cannot reach; `browser-use-stagehand-native` requires a native model name and API key at schema validation with no configuration form on the page.
+
+## 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.
+
+**Mount the registration-only `computer-use` and `browser-use` services in `dsh-base`.** The shared composition would carry rows that only an optional provider bundle needs; a provider bundle can insert the service row from its own patch and dependencies.
+
+## Consequences
+
+The Official group grows from two to three entries, each tagged experimental by its experimental name. The runtime dependency closure of the installation is unchanged. A browser-use or computer-use provider still needs its Service Definition and the provider in a profile patch or composition.
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
new file mode 100644
index 0000000000..62e248e5ae
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md
@@ -0,0 +1,25 @@
+# Agent Note: 将实验能力作为可选 bundle 发布
+
+Status: implemented
+
+[English](2026-09-21-experimental-capabilities-as-optional-bundles.md) | 中文
+
+## 问题
+
+Web 插件页只提供两个可选 bundle:Agent Teams 与语音输入。Auto review 与 Inspector 都是已发布的实验包,用户必须按名称安装或手写 profile patch 才能挂载,尽管两者已经声明了 bundle patch 或附带可挂载的 overlay。
+
+## 决策
+
+`OPTIONAL_BUNDLES` 列出 Agent Teams、语音输入与 Auto review。每个可选 bundle 都声明 `icon` 并导出带 `meta.title` 与 `meta.description` 的 `./locale/*.json`,官方分组因此能渲染本地化标题、描述和图片;`verify-default-product-isolation` 会拒绝缺少这些声明的可选 bundle。Inspector 的 `cordis.patch.yml` 直接写包名而非构建产物路径,所以同一文件既是 bundle patch,也是已构建 Web 启动的 `--patch` overlay。
+
+可选 bundle 是安装的运行时依赖,其依赖图会随每次 `dsh` 安装一起下载。因此名单只接纳依赖图已在安装闭包内的包;Auto review 不增加任何依赖。Inspector 保留显式安装方式,不在默认插件列表中显示。浏览器操作与电脑操作提供方仍保持显式组合:Playwright MCP、Chrome DevTools MCP 与原生 Cua Driver 运行时二进制会让每次安装多出约 85 MB、21 个包,无论 bundle 是否启用;而随附的 Cua Driver MCP 开关会提供一个安装本身并不携带其可执行文件的能力。这些提供方包保留 locale 显示元数据,供组件行使用。另有两个包因其他原因不纳入:`ptc-runtime-python` 会替换 PTC 运行时,而 `workflow-ptc` 在加载时拒绝非 TypeScript 运行时,且 Web preset 内含 bundle patch 无法触及的 `workflow-ptc` 行;`browser-use-stagehand-native` 在 schema 校验时就要求原生模型名称与 API 密钥,而插件页没有对应的配置表单。
+
+## 考虑过的替代方案
+
+**把所有提供方都做成可选 bundle。** 组合与展示都正确,但会为多数安装从不启用的能力把提供方运行时塞进每次安装;语音输入的 `sherpa-onnx-node` 是唯一被接受的先例。
+
+**在 `dsh-base` 中挂载仅负责注册的 `computer-use` 与 `browser-use` 服务。** 共享组合将携带只有可选提供方 bundle 才需要的行;提供方 bundle 可以从自己的 patch 和依赖插入服务行。
+
+## 后果
+
+官方分组从两项增至三项,每项都因实验包名而带实验性标签。安装的运行时依赖闭包保持不变。浏览器操作或电脑操作提供方仍需在 profile patch 或组合中同时挂载其 Service Definition 与提供方。
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 dbe4c5aaab..f5e57341df 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,8 +9,8 @@
en: dc43ba84345e1405
zh: dbc03572ab7710d2
/agent-note-ship-optional-bundles-with-the-installation/decision:
- en: 84777110aa68ecf8
- zh: b109275cfddb73b0
+ en: d64020c4ca4957e1
+ zh: 241b3b851234b6f0
/agent-note-ship-optional-bundles-with-the-installation/alternatives-considered:
en: 05c19f616009bee8
zh: af318640481351f0
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 c9f560f5c0..059fe5bcce 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
@@ -14,7 +14,7 @@ The launcher names in `OPTIONAL_BUNDLES` (`packages/boot/app-boot/src/profile.ts
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 decision](../architecture/2026-09-18-agent-teams-single-bundle.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. Auto review is a published experimental package the page's install guide names as its example, not an optional bundle.
+The [single-bundle decision](../architecture/2026-09-18-agent-teams-single-bundle.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.
## Alternatives considered
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 e91b6580e2..9847095601 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
@@ -14,7 +14,7 @@ Web 插件页只管理用户装进 profile 的组合包。像 Agent Teams 这样
默认产品隔离的规则保持不变,只声明一个例外:可选组合包的依赖图在默认产品之外。静态门禁跳过从 `@deepseek-ai/dsh` 到列表中组合包的 `dependencies` 边,仍然拒绝运行时 import、随附组合、preset 或默认模板对它的引用,拒绝列表没有点名的实验依赖,也拒绝不是运行时依赖或不是组合包的列表项。workspace 约束检查接受同样的 `dependencies` 边而不接受其他运行时依赖段;发布时的 packed-install 检查对已安装入口包跳过这些边,并要求列表中的每个组合包都已安装。
-[单组合包决策](../architecture/2026-09-18-agent-teams-single-bundle.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略。Auto review 是一个已发布的实验包,插件页的安装引导拿它作示例,它不是可选组合包。
+[单组合包决策](../architecture/2026-09-18-agent-teams-single-bundle.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)列出当前名单以及每一项携带的显示元数据。
## 考虑过的替代方案
diff --git a/apps/cli/package.json b/apps/cli/package.json
index fb5f762794..33eac62c0b 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -99,7 +99,8 @@
"@deepseek-ai/dsh-plugin-manager": "workspace:*",
"@deepseek-ai/dsh-atomic-write": "workspace:*",
"@deepseek-ai/dsh-agent-preset": "workspace:*",
- "@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:*"
+ "@deepseek-ai/dsh-experimental-voice-input-bundle": "workspace:*",
+ "@deepseek-ai/dsh-experimental-auto-review": "workspace:*"
},
"devDependencies": {
"@agentclientprotocol/sdk": "1.4.0",
diff --git a/apps/web/tests/expected/plugin-config/official.expected.md b/apps/web/tests/expected/plugin-config/official.expected.md
index dd8775171f..4e53ddd016 100644
--- a/apps/web/tests/expected/plugin-config/official.expected.md
+++ b/apps/web/tests/expected/plugin-config/official.expected.md
@@ -3,12 +3,16 @@
- button "刷新"
- button "添加插件"
- heading "官方" [level=3]
-- text: "6"
+- text: "7"
- list:
- listitem:
- button "查看 智能体团队": 智能体团队
- text: 实验性 启用团队协作、团队工具、成员列表和共享任务看板。
- switch "启用 智能体团队"
+ - listitem:
+ - button "查看 自动授权审查": 自动授权审查
+ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。
+ - switch "启用 自动授权审查"
- listitem:
- button "查看 语音输入": 语音输入
- text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖
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 e9988fe58b..14d7992611 100644
--- a/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md
+++ b/apps/web/tests/expected/plugin-install-cancel/cancelled.expected.md
@@ -3,7 +3,7 @@
- button "关闭"
- paragraph: 输入插件的包名、GitHub 仓库地址或本地目录路径。
- textbox "包名或地址":
- - /placeholder: 例如 @deepseek-ai/dsh-experimental-auto-review
+ - /placeholder: 例如 @deepseek-ai/dsh-subagent-codex
- text: slow-package
- button "插件安装引导和示例"
- 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 fa922891c7..b36f699bb5 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
@@ -2,14 +2,14 @@
- heading "添加插件" [level=2]
- button "关闭"
- textbox "插件包名":
- - /placeholder: 例如 @deepseek-ai/dsh-experimental-auto-review
+ - /placeholder: 例如 @deepseek-ai/dsh-subagent-codex
- button "收起引导" [expanded]
- button "安装源 中国大陆镜像源"
- list:
- listitem:
- text: 包名 插件包名即 npm 包名(如 dsh-xxx 或 @作者/插件名),社区插件的 README 安装命令中 dsh plugin add 或 pnpm add 之后的部分。 示例:
- - code: "@deepseek-ai/dsh-experimental-auto-review"
- - button "填入示例 @deepseek-ai/dsh-experimental-auto-review": 填入示例
+ - code: "@deepseek-ai/dsh-subagent-codex"
+ - button "填入示例 @deepseek-ai/dsh-subagent-codex": 填入示例
- listitem:
- text: GitHub 仓库地址 插件在 GitHub 上的开源仓库地址,也支持其他 Git 仓库。 示例:
- code: https://github.com/author/dsh-plugin
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 fbdc9a5116..b649252d7d 100644
--- a/apps/web/tests/expected/plugin-install-github/mirror.expected.md
+++ b/apps/web/tests/expected/plugin-install-github/mirror.expected.md
@@ -2,7 +2,7 @@
- heading "添加插件" [level=2]
- button "关闭"
- textbox "插件包名":
- - /placeholder: 例如 @deepseek-ai/dsh-experimental-auto-review
+ - /placeholder: 例如 @deepseek-ai/dsh-subagent-codex
- button "插件安装引导和示例"
- button "安装源 中国大陆镜像源"
- button "安装" [disabled]
diff --git a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md
index f7d0ebbe50..e9b345be5f 100644
--- a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md
+++ b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md
@@ -3,12 +3,16 @@
- button "刷新"
- button "添加插件"
- heading "官方" [level=3]
-- text: "6"
+- text: "7"
- list:
- listitem:
- button "查看 智能体团队": 智能体团队
- text: 实验性 启用团队协作、团队工具、成员列表和共享任务看板。
- switch "启用 智能体团队"
+ - listitem:
+ - button "查看 自动授权审查": 自动授权审查
+ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。
+ - switch "启用 自动授权审查"
- listitem:
- button "查看 语音输入": 语音输入
- text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖
diff --git a/apps/web/tests/expected/plugin-manager/manager.expected.md b/apps/web/tests/expected/plugin-manager/manager.expected.md
index 3f33e93302..978daa2f30 100644
--- a/apps/web/tests/expected/plugin-manager/manager.expected.md
+++ b/apps/web/tests/expected/plugin-manager/manager.expected.md
@@ -3,12 +3,16 @@
- button "刷新"
- button "添加插件"
- heading "官方" [level=3]
-- text: "6"
+- text: "7"
- list:
- listitem:
- button "查看 智能体团队": 智能体团队
- text: 实验性 启用团队协作、团队工具、成员列表和共享任务看板。
- switch "启用 智能体团队"
+ - listitem:
+ - button "查看 自动授权审查": 自动授权审查
+ - text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。
+ - switch "启用 自动授权审查"
- listitem:
- button "查看 语音输入": 语音输入
- text: 实验性 在本机使用 SenseVoice 转写录音;首次使用需安装依赖
diff --git a/apps/web/tests/plugin-config.e2e.ts b/apps/web/tests/plugin-config.e2e.ts
index 6e626ce463..3a65d644da 100644
--- a/apps/web/tests/plugin-config.e2e.ts
+++ b/apps/web/tests/plugin-config.e2e.ts
@@ -9,6 +9,7 @@ import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import type { Browser, Locator, Page } from 'playwright'
import { chromium } from 'playwright'
+import { OPTIONAL_BUNDLES } from '@deepseek-ai/dsh-app-boot'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { join } from 'node:path'
import {
@@ -91,7 +92,7 @@ describe('web e2e: plugin configuration pages', () => {
// the official bundles the installation ships switched off.
await panel.getByRole('button', { name: '查看 网页搜索', exact: true }).waitFor({ timeout: 20_000 })
const official = panel.locator('[data-plugin-group="official"]')
- expect(await official.locator('[data-plugin-package]').count()).toBe(2)
+ expect(await official.locator('[data-plugin-package]').count()).toBe(OPTIONAL_BUNDLES.length)
expect(await official.locator('[data-plugin-item]').count()).toBe(4)
for (const title of ['终端', 'Agent 循环', 'Subagent', '网页搜索']) {
expect(await official.getByRole('button', { name: `查看 ${title}`, exact: true }).count()).toBe(1)
diff --git a/apps/web/tests/plugin-manager.e2e.ts b/apps/web/tests/plugin-manager.e2e.ts
index f16e5adaa8..cfa6a9a763 100644
--- a/apps/web/tests/plugin-manager.e2e.ts
+++ b/apps/web/tests/plugin-manager.e2e.ts
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { FiberState } from '@deepseek-ai/cordis'
+import { OPTIONAL_BUNDLES } from '@deepseek-ai/dsh-app-boot'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { join } from 'node:path'
@@ -126,9 +127,11 @@ describe('web e2e: plugin manager', () => {
// optional bundles open the Official group, followed by the official plugins that registered their
// configuration, and its other bundles stay off the page.
expect(await panel.locator('[data-plugin-group="bundles"] [data-plugin-package]').count()).toBe(2)
- expect(await panel.locator('[data-plugin-group="official"] [data-plugin-package]').count()).toBe(2)
+ expect(await panel.locator('[data-plugin-group="official"] [data-plugin-package]').count()).toBe(OPTIONAL_BUNDLES.length)
expect(await panel.locator('[data-plugin-group="official"] [data-plugin-item]').count()).toBe(4)
- expect(await panel.getByText('实验性', { exact: true }).count()).toBe(2)
+ expect(await panel.getByText('实验性', { exact: true }).count())
+ .toBe(OPTIONAL_BUNDLES.filter(name => name.startsWith('@deepseek-ai/dsh-experimental-')).length)
+ expect(await panel.locator('[data-plugin-package="@deepseek-ai/dsh-experimental-inspector"]').count()).toBe(0)
expect(await panel.getByRole('switch', { name: '启用 语音输入', exact: true }).getAttribute('aria-checked')).toBe('false')
// A bundle that is off still shows the rows its patch declares, without switches.
await panel.getByRole('button', { name: '查看 @fixture/bundle' }).click()
diff --git a/packages/boot/app-boot/src/profile.ts b/packages/boot/app-boot/src/profile.ts
index 7406443b26..7211328e04 100644
--- a/packages/boot/app-boot/src/profile.ts
+++ b/packages/boot/app-boot/src/profile.ts
@@ -184,12 +184,15 @@ export const DEFAULT_PROFILE_BUNDLES: readonly string[] = ['@deepseek-ai/dsh-bas
/**
* The bundles the dsh installation ships for a person to switch on: each a
* runtime dependency of the installation that declares `dsh.bundle.patch`,
- * selected by no shipped template, and offered switched off by the plugin
- * manager ([rationale](../../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)).
+ * an `icon`, and `./locale/*.json` display metadata, selected by no shipped
+ * template, and offered switched off by the plugin manager
+ * ([rationale](../../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md),
+ * [admission](../../../../.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md)).
*/
export const OPTIONAL_BUNDLES: readonly string[] = [
- '@deepseek-ai/dsh-experimental-voice-input-bundle',
'@deepseek-ai/dsh-experimental-agent-team-profile',
+ '@deepseek-ai/dsh-experimental-voice-input-bundle',
+ '@deepseek-ai/dsh-experimental-auto-review',
]
const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer:
diff --git a/packages/client/ui-plugin-manager/src/client/locales.ts b/packages/client/ui-plugin-manager/src/client/locales.ts
index 23e02c430a..3c4fb304f0 100644
--- a/packages/client/ui-plugin-manager/src/client/locales.ts
+++ b/packages/client/ui-plugin-manager/src/client/locales.ts
@@ -48,11 +48,11 @@ export const zh = {
installTitle: '添加插件',
installDescription: '输入插件的包名、GitHub 仓库地址或本地目录路径。',
installSpecLabel: '包名或地址',
- installSpecPlaceholder: '例如 @deepseek-ai/dsh-experimental-auto-review',
+ installSpecPlaceholder: '例如 @deepseek-ai/dsh-subagent-codex',
installGuideToggle: '插件安装引导和示例',
installGuideHide: '收起引导',
installGuideIdTitle: '包名',
- installGuideIdExample: '@deepseek-ai/dsh-experimental-auto-review',
+ installGuideIdExample: '@deepseek-ai/dsh-subagent-codex',
installGuideIdHint: '插件包名即 npm 包名(如 dsh-xxx 或 @作者/插件名),社区插件的 README 安装命令中 dsh plugin add 或 pnpm add 之后的部分。',
installGuideGitTitle: 'GitHub 仓库地址',
installGuideGitExample: 'https://github.com/author/dsh-plugin',
@@ -241,11 +241,11 @@ export const en = {
installTitle: 'Add plugin',
installDescription: 'Enter the plugin\'s package name, GitHub repository address, or local directory path.',
installSpecLabel: 'Package name or address',
- installSpecPlaceholder: 'for example @deepseek-ai/dsh-experimental-auto-review',
+ installSpecPlaceholder: 'for example @deepseek-ai/dsh-subagent-codex',
installGuideToggle: 'Install guide and examples',
installGuideHide: 'Hide the guide',
installGuideIdTitle: 'Package name',
- installGuideIdExample: '@deepseek-ai/dsh-experimental-auto-review',
+ installGuideIdExample: '@deepseek-ai/dsh-subagent-codex',
installGuideIdHint: 'The plugin package name is the npm package name (like dsh-xxx or @author/plugin): the part after dsh plugin add or pnpm add in a community plugin\'s README install command.',
installGuideGitTitle: 'GitHub repository address',
installGuideGitExample: 'https://github.com/author/dsh-plugin',
diff --git a/packages/experimental/README.i18n.yaml b/packages/experimental/README.i18n.yaml
index ee03f48578..c3c5329e44 100644
--- a/packages/experimental/README.i18n.yaml
+++ b/packages/experimental/README.i18n.yaml
@@ -9,8 +9,8 @@
en: 18bc48bb66b3e516
zh: 51f2340a5c921f36
/packages-experimental/summary:
- en: bf80f4f1c888ff86
- zh: 6fa03019ffab7207
+ en: 9341a408afcd5dfa
+ zh: 07541055b5f41ff8
/packages-experimental/table-of-contents:
en: 1eb35b015a6c423f
zh: a8a3dceee2c9e943
diff --git a/packages/experimental/README.md b/packages/experimental/README.md
index a3fc9ee815..a49181f7de 100644
--- a/packages/experimental/README.md
+++ b/packages/experimental/README.md
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-The experimental group contains prototype capabilities whose contracts can change and carry no support promise. All current packages publish under their `@deepseek-ai/dsh-experimental-*` names, including the opt-in Agent Teams composition, Auto review, Cua Driver providers, browser-use backends, cross-realm Inspector, CPython PTC backend, and browser-worker preview libraries. Released products outside this group must not depend on experimental packages.
+The experimental group contains prototype capabilities whose contracts can change and carry no support promise. All current packages publish under their `@deepseek-ai/dsh-experimental-*` names, including the opt-in Agent Teams composition, Auto review, Cua Driver providers, browser-use backends, cross-realm Inspector, CPython PTC backend, and browser-worker preview libraries. Released products outside this group must not depend on experimental packages. The dsh installation ships the Agent Teams, voice input, and Auto review packages as optional bundles switched on from the Web sidebar's Plugins page ([decision](../../.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md)); the other packages are libraries or explicit compositions.
## Table of Contents
diff --git a/packages/experimental/README.zh.md b/packages/experimental/README.zh.md
index d038735fc4..68b905673f 100644
--- a/packages/experimental/README.zh.md
+++ b/packages/experimental/README.zh.md
@@ -9,7 +9,7 @@ kind: "package-group"
## 概述
-实验组包含约定可能变更且不提供支持承诺的原型能力。所有当前包都以 `@deepseek-ai/dsh-experimental-*` 名称发布,包括显式启用的 Agent Teams 组合、Auto review、Cua Driver 提供方、浏览器操作后端、跨 realm Inspector、CPython PTC 后端与浏览器 worker 预览库。组外已发布产品不得依赖实验性包。
+实验组包含约定可能变更且不提供支持承诺的原型能力。所有当前包都以 `@deepseek-ai/dsh-experimental-*` 名称发布,包括显式启用的 Agent Teams 组合、Auto review、Cua Driver 提供方、浏览器操作后端、跨 realm Inspector、CPython PTC 后端与浏览器 worker 预览库。组外已发布产品不得依赖实验性包。dsh 安装将 Agent Teams、语音输入与 Auto review 包作为可选 bundle 一起发布,可从 Web 侧边栏“插件”页启用([决策](../../.agents/notes/implemented/architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md));其余包是库或显式组合。
## 目录
diff --git a/packages/experimental/auto-review/icon.svg b/packages/experimental/auto-review/icon.svg
new file mode 100644
index 0000000000..352561fe59
--- /dev/null
+++ b/packages/experimental/auto-review/icon.svg
@@ -0,0 +1,10 @@
+
diff --git a/packages/experimental/auto-review/package.json b/packages/experimental/auto-review/package.json
index 2a94ae80d9..fef2d2426b 100644
--- a/packages/experimental/auto-review/package.json
+++ b/packages/experimental/auto-review/package.json
@@ -2,6 +2,7 @@
"name": "@deepseek-ai/dsh-experimental-auto-review",
"description": "Per-tool LLM authorization review for the DeepSeek Harness Auto permission preset",
"version": "0.1.7-rc.1",
+ "icon": "./icon.svg",
"repository": {
"type": "git",
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
@@ -21,6 +22,7 @@
"./cordis.patch.yml": "./cordis.patch.yml"
},
"files": [
+ "icon.svg",
"locale/*.json",
"lib/index.js",
"cordis.patch.yml",
diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/locale/en.json b/packages/experimental/browser-use-chrome-devtools-mcp/locale/en.json
new file mode 100644
index 0000000000..a2f98e1846
--- /dev/null
+++ b/packages/experimental/browser-use-chrome-devtools-mcp/locale/en.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Browser Use (Chrome DevTools)",
+ "description": "Let the model inspect and operate web pages through Chrome DevTools MCP in a launched Chrome."
+ }
+}
diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/locale/zh.json b/packages/experimental/browser-use-chrome-devtools-mcp/locale/zh.json
new file mode 100644
index 0000000000..85386f228e
--- /dev/null
+++ b/packages/experimental/browser-use-chrome-devtools-mcp/locale/zh.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "浏览器操作(Chrome DevTools)",
+ "description": "让模型通过 Chrome DevTools MCP 在新启动的 Chrome 中检查和操作网页。"
+ }
+}
diff --git a/packages/experimental/browser-use-chrome-devtools-mcp/package.json b/packages/experimental/browser-use-chrome-devtools-mcp/package.json
index 91966e1436..1ad0ce87a7 100644
--- a/packages/experimental/browser-use-chrome-devtools-mcp/package.json
+++ b/packages/experimental/browser-use-chrome-devtools-mcp/package.json
@@ -19,9 +19,11 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./locale/*.json": "./locale/*.json"
},
"files": [
+ "locale/*.json",
"lib/index.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/experimental/browser-use-playwright-mcp/locale/en.json b/packages/experimental/browser-use-playwright-mcp/locale/en.json
new file mode 100644
index 0000000000..ffc27dd81c
--- /dev/null
+++ b/packages/experimental/browser-use-playwright-mcp/locale/en.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Browser Use (Playwright)",
+ "description": "Let the model browse and operate web pages through Playwright MCP in a launched Chromium."
+ }
+}
diff --git a/packages/experimental/browser-use-playwright-mcp/locale/zh.json b/packages/experimental/browser-use-playwright-mcp/locale/zh.json
new file mode 100644
index 0000000000..65bc895c0b
--- /dev/null
+++ b/packages/experimental/browser-use-playwright-mcp/locale/zh.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "浏览器操作(Playwright)",
+ "description": "让模型通过 Playwright MCP 在新启动的 Chromium 中浏览和操作网页。"
+ }
+}
diff --git a/packages/experimental/browser-use-playwright-mcp/package.json b/packages/experimental/browser-use-playwright-mcp/package.json
index 17f86e295e..50b3bb331c 100644
--- a/packages/experimental/browser-use-playwright-mcp/package.json
+++ b/packages/experimental/browser-use-playwright-mcp/package.json
@@ -19,9 +19,11 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./locale/*.json": "./locale/*.json"
},
"files": [
+ "locale/*.json",
"lib/index.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/experimental/computer-use-cua-driver-mcp/locale/en.json b/packages/experimental/computer-use-cua-driver-mcp/locale/en.json
new file mode 100644
index 0000000000..9ae3ab4ed1
--- /dev/null
+++ b/packages/experimental/computer-use-cua-driver-mcp/locale/en.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Computer Use (Cua Driver MCP)",
+ "description": "Let the model operate the desktop through a separately installed cua-driver executable."
+ }
+}
diff --git a/packages/experimental/computer-use-cua-driver-mcp/locale/zh.json b/packages/experimental/computer-use-cua-driver-mcp/locale/zh.json
new file mode 100644
index 0000000000..6d9e10e79d
--- /dev/null
+++ b/packages/experimental/computer-use-cua-driver-mcp/locale/zh.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "电脑操作(Cua Driver MCP)",
+ "description": "让模型通过单独安装的 cua-driver 可执行文件操作桌面。"
+ }
+}
diff --git a/packages/experimental/computer-use-cua-driver-mcp/package.json b/packages/experimental/computer-use-cua-driver-mcp/package.json
index 75c08ecfee..a5c4bf0a92 100644
--- a/packages/experimental/computer-use-cua-driver-mcp/package.json
+++ b/packages/experimental/computer-use-cua-driver-mcp/package.json
@@ -19,9 +19,11 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./locale/*.json": "./locale/*.json"
},
"files": [
+ "locale/*.json",
"lib/index.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/experimental/computer-use-cua-driver-native/locale/en.json b/packages/experimental/computer-use-cua-driver-native/locale/en.json
new file mode 100644
index 0000000000..009e83af75
--- /dev/null
+++ b/packages/experimental/computer-use-cua-driver-native/locale/en.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Computer Use (Cua Driver)",
+ "description": "Let the model operate the desktop with the bundled Cua Driver runtime; needs desktop permissions for the app that launches dsh."
+ }
+}
diff --git a/packages/experimental/computer-use-cua-driver-native/locale/zh.json b/packages/experimental/computer-use-cua-driver-native/locale/zh.json
new file mode 100644
index 0000000000..9e8349ee1a
--- /dev/null
+++ b/packages/experimental/computer-use-cua-driver-native/locale/zh.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "电脑操作(Cua Driver)",
+ "description": "让模型使用内置的 Cua Driver 运行时操作桌面;需要为启动 dsh 的应用授予桌面权限。"
+ }
+}
diff --git a/packages/experimental/computer-use-cua-driver-native/package.json b/packages/experimental/computer-use-cua-driver-native/package.json
index 21005de10f..1a3f92ba48 100644
--- a/packages/experimental/computer-use-cua-driver-native/package.json
+++ b/packages/experimental/computer-use-cua-driver-native/package.json
@@ -19,9 +19,11 @@
"default": "./lib/index.js"
},
"./src/*": "./src/*",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./locale/*.json": "./locale/*.json"
},
"files": [
+ "locale/*.json",
"lib/index.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/experimental/inspector/README.i18n.yaml b/packages/experimental/inspector/README.i18n.yaml
index 8d56bf815a..626859a4e3 100644
--- a/packages/experimental/inspector/README.i18n.yaml
+++ b/packages/experimental/inspector/README.i18n.yaml
@@ -3,20 +3,20 @@
# After editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/experimental/inspector/README.md
/:
- en: b5b7ccdfa1cc2907
- zh: d575593a7efa37fd
+ en: ff4e1ec5ccd5d0b3
+ zh: d29383843e3decc8
/deepseek-ai-dsh-experimental-inspector:
en: 1060bee3ef37134d
zh: 7a7b0096714f4279
/deepseek-ai-dsh-experimental-inspector/summary:
- en: 71238c294240dad0
- zh: 382fc8f003ebf0ab
+ en: 65ed8a4c21915cc0
+ zh: dcb1ae527e6bfc8c
/deepseek-ai-dsh-experimental-inspector/table-of-contents:
en: 2650c2b268bf6dff
zh: 43a08863d2c75ad1
/deepseek-ai-dsh-experimental-inspector/runtime-layout:
- en: ddec7e27dfb22d30
- zh: 37970b25c76b03b0
+ en: a713d2910acf48c9
+ zh: 152dcdba78e73115
/deepseek-ai-dsh-experimental-inspector/configuration:
en: 95f1c9c4ea6ba8b8
zh: 16b56e2dd8c7408a
diff --git a/packages/experimental/inspector/README.md b/packages/experimental/inspector/README.md
index 2b2986d49b..6a16e5f7c4 100644
--- a/packages/experimental/inspector/README.md
+++ b/packages/experimental/inspector/README.md
@@ -1,6 +1,6 @@
---
description: "Experimental Chrome DevTools inspection for Host and browser Client Cordis runtimes, including Console evaluation, Sources, Network capture, Elements trees, and a CDP-independent query API."
-kind: "package-reference"
+kind: "package-bundle"
---
# @deepseek-ai/dsh-experimental-inspector
@@ -9,9 +9,9 @@ English | [中文](README.zh.md)
## Summary
-Use this experimental inspector to inspect one running dsh Host and its browser Clients in Chrome DevTools. It exposes Host and Client Console contexts, Host Sources and debugging, captured Host fetches, and a shared Cordis tree while keeping all CDP state in a Worker.
+Inspect one running dsh Host and its browser Clients in Chrome DevTools: Host and Client Console contexts, Host Sources and debugging, captured Host fetches, and a shared Cordis tree, with all CDP state in a Worker.
-The package publishes under its experimental name and requires an explicit composition. The Worker never accesses live Cordis objects: the shared Host/Client collector projects them into validated snapshots before transport. Cordis also owns plugin composition, `ctx.inspector` registration, bootstrap injection, and disposal.
+The Inspector stays off the default plugin list. Install its bundle explicitly with `dsh plugin --profile web add @deepseek-ai/dsh-experimental-inspector`. Its [`cordis.patch.yml`](cordis.patch.yml) mounts the installed package; `pnpm run demo:inspector` mounts the source tree. The Host row needs a Web server. The Worker never accesses live Cordis objects: the shared collector projects them into validated snapshots before transport.
## Table of Contents
@@ -30,7 +30,7 @@ The package publishes under its experimental name and requires an explicit compo
## Runtime layout
-The Host plugin starts the Worker and connects a dedicated `MessagePort`. The Client plugin reads the injected `globalThis.__DSH_INSPECTOR__` bootstrap and opens a separate authenticated WebSocket directly to the Worker. Chrome DevTools connects to the Worker's CDP WebSocket. A private `node:inspector.Session` per DevTools connection attaches from the Worker to the Host main thread, so Host Console evaluation, Sources, breakpoints, and resume remain available while Host JavaScript is paused.
+The Host plugin starts the Worker and connects a dedicated `MessagePort`. The Client plugin reads the injected `globalThis.__DSH_INSPECTOR__` bootstrap and opens a separate authenticated WebSocket directly to the Worker; a page opened before the Host row was enabled has no bootstrap, fails its Client plugin with a reload instruction, and inspects only after a reload. Chrome DevTools connects to the Worker's CDP WebSocket. A private `node:inspector.Session` per DevTools connection attaches from the Worker to the Host main thread, so Host Console evaluation, Sources, breakpoints, and resume remain available while Host JavaScript is paused.
The source tree follows those execution environments: `client/` and `host/` provide mirrored adapter entry paths, `worker/` contains only Worker-thread orchestration and Chrome protocol state, and `shared/` contains environment-independent Cordis and network models, normalized realm backend interfaces, and the internal bridge protocol. Worker-side Client and Host adapters are mirrored under `worker/realms/`; a Client adapter in that directory still executes in the Worker.
diff --git a/packages/experimental/inspector/README.zh.md b/packages/experimental/inspector/README.zh.md
index a24b17d3a8..05756181e1 100644
--- a/packages/experimental/inspector/README.zh.md
+++ b/packages/experimental/inspector/README.zh.md
@@ -1,6 +1,6 @@
---
description: "面向 Host 与浏览器 Client Cordis 运行时的实验性 Chrome DevTools 检查,包括 Console 求值、Sources、Network 采集、Elements 树和独立于 CDP 的查询 API。"
-kind: "package-reference"
+kind: "package-bundle"
---
# @deepseek-ai/dsh-experimental-inspector
@@ -9,9 +9,9 @@ kind: "package-reference"
## 概述
-使用这个实验性 Inspector,可以在 Chrome DevTools 中检查一个运行中的 dsh Host 及其浏览器 Client。它提供 Host 与 Client Console context、Host Sources 与调试、Host fetch 采集和共享 Cordis 树,并让 Worker 独占全部 CDP 状态。
+在 Chrome DevTools 中检查一个运行中的 dsh Host 及其浏览器 Client:Host 与 Client Console context、Host Sources 与调试、Host fetch 采集和共享 Cordis 树,全部 CDP 状态都在 Worker 中。
-本包以实验性名称发布,需要显式组合。Worker 不访问实时 Cordis 对象;共享 Host/Client collector 会在传输前把它们投影成已验证快照。Cordis 还负责插件组合、注册 `ctx.inspector`、注入 bootstrap 和 dispose(资源释放)。
+Inspector 不在默认插件列表中显示。使用 `dsh plugin --profile web add @deepseek-ai/dsh-experimental-inspector` 显式安装其 bundle。其 [`cordis.patch.yml`](cordis.patch.yml) 挂载已安装的包;`pnpm run demo:inspector` 则挂载源码树。Host 行需要 Web 服务器。Worker 不访问实时 Cordis 对象;共享 collector 会在传输前把它们投影成已验证快照。
## 目录
@@ -30,7 +30,7 @@ kind: "package-reference"
## 运行时布局
-Host 插件启动 Worker 并连接专用 `MessagePort`。Client 插件读取注入的 `globalThis.__DSH_INSPECTOR__` bootstrap,直接向 Worker 打开一条独立、带鉴权的 WebSocket。Chrome DevTools 连接 Worker 的 CDP WebSocket。每条 DevTools 连接在 Worker 中独占一个连接 Host 主线程的 `node:inspector.Session`,因此 Host JavaScript 暂停时,Host Console 求值、Sources、断点和 resume 仍然可用。
+Host 插件启动 Worker 并连接专用 `MessagePort`。Client 插件读取注入的 `globalThis.__DSH_INSPECTOR__` bootstrap,直接向 Worker 打开一条独立、带鉴权的 WebSocket;在 Host 行启用前打开的页面没有 bootstrap,其 Client 插件会以刷新提示失败,刷新后才能被检查。Chrome DevTools 连接 Worker 的 CDP WebSocket。每条 DevTools 连接在 Worker 中独占一个连接 Host 主线程的 `node:inspector.Session`,因此 Host JavaScript 暂停时,Host Console 求值、Sources、断点和 resume 仍然可用。
源码树遵循这些执行环境:`client/` 与 `host/` 提供镜像的适配器 entry path,`worker/` 只包含 Worker thread orchestration 与 Chrome protocol 状态,`shared/` 包含与环境无关的 Cordis 和 network model、规范化 realm 后端接口及内部 bridge protocol。Worker 侧 Client 与 Host 适配器镜像放在 `worker/realms/` 下;其中的 Client 适配器仍然在 Worker 中执行。
diff --git a/packages/experimental/inspector/cordis.patch.yml b/packages/experimental/inspector/cordis.patch.yml
index fc66cb0d06..6586192f90 100644
--- a/packages/experimental/inspector/cordis.patch.yml
+++ b/packages/experimental/inspector/cordis.patch.yml
@@ -1,8 +1,6 @@
-# Built-artifact overlay for the experimental inspector:
-# node apps/cli/lib/bin.js web --patch ./packages/experimental/inspector/cordis.patch.yml
-# The relative entry is anchored to this file, so the private package does not
-# need to be installed into the selected profile. Run `pnpm run build` first.
+# Explicitly installed bundle: the cross-realm inspector. The Host row
+# activates only where a Web server exists.
- insert:
- id: experimental-inspector
- name: './lib/index.js'
+ name: '@deepseek-ai/dsh-experimental-inspector'
diff --git a/packages/experimental/inspector/icon.svg b/packages/experimental/inspector/icon.svg
new file mode 100644
index 0000000000..5d50e0fa5a
--- /dev/null
+++ b/packages/experimental/inspector/icon.svg
@@ -0,0 +1,12 @@
+
diff --git a/packages/experimental/inspector/locale/en.json b/packages/experimental/inspector/locale/en.json
new file mode 100644
index 0000000000..2f601dff21
--- /dev/null
+++ b/packages/experimental/inspector/locale/en.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "Inspector",
+ "description": "Open the running Host and its browser Clients in Chrome DevTools through a loopback endpoint that allows code execution and captures Host network traffic, including secrets. For developers; Web only; reload open pages after enabling."
+ }
+}
diff --git a/packages/experimental/inspector/locale/zh.json b/packages/experimental/inspector/locale/zh.json
new file mode 100644
index 0000000000..5bf93f610b
--- /dev/null
+++ b/packages/experimental/inspector/locale/zh.json
@@ -0,0 +1,6 @@
+{
+ "meta": {
+ "title": "调试检查器",
+ "description": "通过一个允许执行代码并捕获 Host 网络请求(含密钥)的本机回环端点,在 Chrome DevTools 中检查运行中的 Host 及其浏览器 Client。面向开发者;仅 Web 可用;启用后需刷新已打开的页面。"
+ }
+}
diff --git a/packages/experimental/inspector/package.json b/packages/experimental/inspector/package.json
index ada5657242..b6b14dd881 100644
--- a/packages/experimental/inspector/package.json
+++ b/packages/experimental/inspector/package.json
@@ -2,6 +2,7 @@
"name": "@deepseek-ai/dsh-experimental-inspector",
"description": "Experimental cross-realm CDP hub for Host debugging and Client Runtime inspection",
"version": "0.1.7-rc.1",
+ "icon": "./icon.svg",
"publishConfig": {
"access": "public"
},
@@ -23,18 +24,26 @@
"default": "./lib/client.js"
},
"./src/*": "./src/*",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./cordis.patch.yml": "./cordis.patch.yml",
+ "./locale/*.json": "./locale/*.json"
},
"dsh": {
"client": {
"inject": [],
"platform": "web",
"immediately": true
+ },
+ "bundle": {
+ "patch": "./cordis.patch.yml"
}
},
"files": [
+ "icon.svg",
+ "locale/*.json",
"lib/index.js",
"lib/client.js",
+ "cordis.patch.yml",
"lib/worker.js",
"lib/types/**/*.d.ts"
],
diff --git a/packages/experimental/inspector/src/client/plugin.ts b/packages/experimental/inspector/src/client/plugin.ts
index c183dce5ec..d4656de9f6 100644
--- a/packages/experimental/inspector/src/client/plugin.ts
+++ b/packages/experimental/inspector/src/client/plugin.ts
@@ -45,7 +45,7 @@ export const inject: string[] = []
export async function apply(ctx: Context): Promise {
const injected = globalThis.__DSH_INSPECTOR__
if (injected === undefined) {
- throw new Error('experimental inspector: Host bootstrap is missing')
+ throw new Error('experimental inspector: Host bootstrap is missing; reload the page after enabling the Inspector')
}
const bootstrap = parseInspectorClientBootstrap(injected)
await ctx.effect(async () => {
diff --git a/packages/experimental/inspector/tests/loader-composition.host.spec.ts b/packages/experimental/inspector/tests/loader-composition.host.spec.ts
index 35fa9103b5..387482d6e0 100644
--- a/packages/experimental/inspector/tests/loader-composition.host.spec.ts
+++ b/packages/experimental/inspector/tests/loader-composition.host.spec.ts
@@ -3,10 +3,11 @@
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
-import { pathToFileURL } from 'node:url'
+import { fileURLToPath, pathToFileURL } from 'node:url'
import { Context } from '@deepseek-ai/cordis'
import Include from '@deepseek-ai/cordis-plugin-include'
import Loader from '@deepseek-ai/cordis-plugin-loader'
+import { loadOverlayPatches } from '@deepseek-ai/dsh-app-boot'
import WebServer from '@deepseek-ai/dsh-host-webserver'
import { afterEach, describe, expect, it, vi } from 'vitest'
import * as Inspector from '../src/index.ts'
@@ -22,7 +23,7 @@ afterEach(async () => {
})
describe('experimental Inspector through a real Loader composition', () => {
- it('loads the named-export Host face from cordis.yml and releases its endpoint', async () => {
+ it('mounts the bundle patch over a Web server row and releases its endpoint', async () => {
root = await mkdtemp(join(tmpdir(), 'dsh-inspector-loader-'))
const configPath = join(root, 'cordis.yml')
await writeFile(configPath, [
@@ -30,12 +31,14 @@ describe('experimental Inspector through a real Loader composition', () => {
' config:',
" host: '127.0.0.1'",
' port: 0',
- "- name: '@deepseek-ai/dsh-experimental-inspector'",
- ' config:',
- ' port: 0',
- ' captureFetch: false',
'',
].join('\n'))
+ // The shipped bundle patch inserts the row by package name under a stable id; a later
+ // profile patch configures that id, as the Plugins page and a `--patch` overlay do.
+ const bundlePatches = loadOverlayPatches('test', fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)))
+ expect(bundlePatches.flatMap(patch => patch.insert ?? [])).toEqual([
+ { id: 'experimental-inspector', name: '@deepseek-ai/dsh-experimental-inspector' },
+ ])
context = new Context()
context.baseUrl = pathToFileURL(root).href + '/'
@@ -62,7 +65,10 @@ describe('experimental Inspector through a real Loader composition', () => {
} as unknown as NonNullable
await context.loader.create({
name: 'cordis:include',
- config: { path: pathToFileURL(configPath).href },
+ config: {
+ path: pathToFileURL(configPath).href,
+ patches: [...bundlePatches, { id: 'experimental-inspector', config: { port: 0, captureFetch: false } }],
+ },
})
await context.loader.await()
diff --git a/packages/experimental/inspector/tests/plugin.client.spec.ts b/packages/experimental/inspector/tests/plugin.client.spec.ts
index f997229925..7d518e976f 100644
--- a/packages/experimental/inspector/tests/plugin.client.spec.ts
+++ b/packages/experimental/inspector/tests/plugin.client.spec.ts
@@ -440,7 +440,7 @@ describe('experimental Inspector Client plugin', () => {
globalThis.WebSocket = FakeWebSocket as unknown as typeof WebSocket
const ctx = new Context()
const fiber = ctx.plugin({ apply })
- await expect(fiber).rejects.toThrow('Host bootstrap is missing')
+ await expect(fiber).rejects.toThrow('Host bootstrap is missing; reload the page after enabling the Inspector')
await fiber.dispose()
})
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3f39c63fdc..e18a66b8f9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -244,6 +244,9 @@ importers:
'@deepseek-ai/dsh-experimental-agent-team-profile':
specifier: workspace:*
version: link:../../packages/experimental/agent-team-profile
+ '@deepseek-ai/dsh-experimental-auto-review':
+ specifier: workspace:*
+ version: link:../../packages/experimental/auto-review
'@deepseek-ai/dsh-experimental-voice-input-bundle':
specifier: workspace:*
version: link:../../packages/experimental/voice-input-bundle
@@ -19031,13 +19034,9 @@ packages:
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
- glob@7.2.0:
- resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
- deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
-
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@@ -26456,15 +26455,6 @@ snapshots:
package-json-from-dist: 1.0.1
path-scurry: 1.11.1
- glob@7.2.0:
- dependencies:
- fs.realpath: 1.0.0
- inflight: 1.0.6
- inherits: 2.0.4
- minimatch: 3.1.5
- once: 1.4.0
- path-is-absolute: 1.0.1
-
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
@@ -28200,7 +28190,7 @@ snapshots:
rimraf@2.6.3:
dependencies:
- glob: 7.2.0
+ glob: 7.2.3
roarr@2.15.4:
dependencies:
diff --git a/scripts/optional-bundles.spec.ts b/scripts/optional-bundles.spec.ts
new file mode 100644
index 0000000000..71671e5b71
--- /dev/null
+++ b/scripts/optional-bundles.spec.ts
@@ -0,0 +1,62 @@
+/** Every bundle the installation ships switched off composes over the shipped Web layers and carries display metadata. */
+
+import { globSync, readFileSync } from 'node:fs'
+import { dirname, resolve } from 'node:path'
+import { pathToFileURL } from 'node:url'
+import { describe, expect, it } from 'vitest'
+import { loadOverlayPatches } from '../packages/boot/app-boot/src/index.ts'
+import { readPluginMeta } from '../packages/boot/app-boot/src/package-meta.ts'
+import { OPTIONAL_BUNDLES, bundlePatchPaths, composeEntries } from '../packages/boot/app-boot/src/profile.ts'
+import type { DshBundleManifest } from '../packages/util/package-manifest/src/types.ts'
+
+const root = resolve(import.meta.dirname, '..')
+
+interface Manifest {
+ name: string
+ dsh?: { bundle?: DshBundleManifest }
+}
+
+const bundles = new Map(globSync('packages/*/*/package.json', { cwd: root }).map((path) => {
+ const manifest = JSON.parse(readFileSync(resolve(root, path), 'utf8')) as Manifest
+ return [manifest.name, { dir: dirname(resolve(root, path)), manifest }]
+}))
+
+function bundle(name: string): { dir: string; patches: ReturnType } {
+ const entry = bundles.get(name)
+ if (entry?.manifest.dsh?.bundle === undefined) throw new Error(`${name} is not a workspace bundle`)
+ return { dir: entry.dir, patches: bundlePatchPaths(entry.dir, entry.manifest.dsh.bundle).flatMap(path => loadOverlayPatches('test', path)) }
+}
+
+describe('optional bundles', () => {
+ const shipped = ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'].map(name => bundle(name).patches)
+
+ it('ships at least one bundle switched off', () => {
+ expect(OPTIONAL_BUNDLES.length).toBeGreaterThan(0)
+ })
+
+ it('keeps the Inspector out of the default plugin list', () => {
+ expect(OPTIONAL_BUNDLES).not.toContain('@deepseek-ai/dsh-experimental-inspector')
+ })
+
+ it.each(OPTIONAL_BUNDLES)('%s composes over the Web profile without a skipped patch', (name) => {
+ const { patches } = bundle(name)
+ const warnings: string[] = []
+ const ids = new Set(composeEntries([...shipped, patches], message => warnings.push(message)).map(entry => entry.id))
+ expect(warnings).toEqual([])
+ // Inserted rows carry stable ids at the profile root, so a later profile patch can configure or disable them.
+ for (const row of patches.flatMap(patch => patch.insert ?? [])) {
+ expect(typeof row.id).toBe('string')
+ expect(ids.has(row.id)).toBe(true)
+ }
+ })
+
+ it.each(OPTIONAL_BUNDLES)('%s resolves a title, description, and icon in both shipped languages', (name) => {
+ const meta = readPluginMeta(name, pathToFileURL(`${bundle(name).dir}/package.json`).href)
+ expect(meta?.error).toBeUndefined()
+ for (const field of [meta?.title, meta?.description]) {
+ expect(typeof field).toBe('object')
+ for (const language of ['en', 'zh']) expect((field as Record)[language]).toMatch(/\S/)
+ }
+ expect(meta?.icon).toMatch(/^data:image\//)
+ })
+})
diff --git a/scripts/verify-default-product-isolation.spec.ts b/scripts/verify-default-product-isolation.spec.ts
index f3ac184ae1..bd03908d50 100644
--- a/scripts/verify-default-product-isolation.spec.ts
+++ b/scripts/verify-default-product-isolation.spec.ts
@@ -95,7 +95,8 @@ describe('default product isolation', () => {
const root = fixture()
const layer = '@deepseek-ai/dsh-experimental-layer'
write(root, 'packages/experimental/layer/package.json', {
- name: layer, dependencies: { [experimental]: 'workspace:^' }, dsh: { bundle: { patch: './cordis.patch.yml' } },
+ name: layer, icon: './icon.svg', exports: { './locale/*.json': './locale/*.json' },
+ dependencies: { [experimental]: 'workspace:^' }, dsh: { bundle: { patch: './cordis.patch.yml' } },
})
write(root, 'packages/experimental/layer/cordis.patch.yml', [{ insert: [{ name: experimental }] }])
manifest(root, 'apps/cli/package.json', { dependencies: { [core]: 'workspace:^', [layer]: 'workspace:^' } })
@@ -114,7 +115,7 @@ describe('default product isolation', () => {
expect(verifyDefaultProductIsolation(root).failures.join('\n')).toContain(`optional bundle ${layer} must not be a default bundle`)
})
- it('requires each optional bundle to be a runtime dependency that declares a bundle patch', () => {
+ it('requires each optional bundle to be a runtime dependency that declares a bundle patch, an icon, and locale metadata', () => {
const root = fixture()
write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
+ `export const DEFAULT_PROFILE_BUNDLES = ['${base}']\n`
@@ -122,6 +123,8 @@ describe('default product isolation', () => {
const failures = verifyDefaultProductIsolation(root).failures.join('\n')
expect(failures).toContain(`optional bundle ${experimental} must be a runtime dependency of apps/cli`)
expect(failures).toContain(`optional bundle ${experimental} must declare dsh.bundle.patch`)
+ expect(failures).toContain(`optional bundle ${experimental} must declare an icon`)
+ expect(failures).toContain(`optional bundle ${experimental} must export ./locale/*.json display metadata`)
// An experimental runtime dependency the list does not name is still a product requirement.
write(root, profile, `export const PROFILE_TEMPLATES = { web: { bundles: ['${base}'] } }\n`
diff --git a/scripts/verify-default-product-isolation.ts b/scripts/verify-default-product-isolation.ts
index 1fe49fb2b8..4f07d07cd8 100644
--- a/scripts/verify-default-product-isolation.ts
+++ b/scripts/verify-default-product-isolation.ts
@@ -29,6 +29,8 @@ const RUNTIME_SECTIONS = ['dependencies', 'optionalDependencies', 'peerDependenc
interface Manifest {
name: string
+ icon?: unknown
+ exports?: Record
dependencies?: Record
optionalDependencies?: Record
peerDependencies?: Record
@@ -79,7 +81,8 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
if (cli?.manifest.name !== '@deepseek-ai/dsh') {
failures.push('apps/cli/package.json must identify @deepseek-ai/dsh')
}
- // The bundles the launcher ships switched off: each a runtime dependency of the installation that is a bundle, none a default.
+ // The bundles the launcher ships switched off: each a runtime dependency of the installation that is a bundle
+ // with an icon and locale display metadata for the plugin manager's Official group, none a default.
const profilePath = resolve(root, PROFILE_SOURCE)
const selection = existsSync(profilePath) ? profilePackages(readFileSync(profilePath, 'utf8')) : undefined
const optionalBundles = new Set(selection?.optionalBundles ?? [])
@@ -87,9 +90,16 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
if (cli?.manifest.dependencies?.[name] === undefined) {
failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must be a runtime dependency of apps/cli`)
}
- if (packages.get(name)?.manifest.dsh?.bundle?.patch === undefined) {
+ const manifest = packages.get(name)?.manifest
+ if (manifest?.dsh?.bundle?.patch === undefined) {
failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must declare dsh.bundle.patch`)
}
+ if (typeof manifest?.icon !== 'string') {
+ failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must declare an icon`)
+ }
+ if (manifest?.exports?.['./locale/*.json'] === undefined) {
+ failures.push(`${PROFILE_SOURCE}: optional bundle ${name} must export ./locale/*.json display metadata`)
+ }
}
const queue: Package[] = []