fix(bundle): exclude product subagents from base

The Codex and Claude Code subagent providers were production dependencies of
@deepseek-ai/dsh-base and mounted by its Cordis composition, so every install of
the base bundle carried two providers that only some products want.

Drop both from the base bundle's dependencies and composition. The examples keep
them as explicit dependencies, base's tests lock their absence, and the product
preset e2e mounts the providers it needs explicitly.

Cherry-picked from #2387 (two commits squashed into one).
This commit is contained in:
imccyu
2026-08-13 14:05:46 +08:00
parent a312675d10
commit 1646b7617e
8 changed files with 83 additions and 34 deletions
@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md
2026-08-12-production-dsh-excludes-product-subagent-providers.md: 3e3e4fbefb31932a637bfe05ff0d90916e202a79
2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md: 166964675bf7084b62f5500969e5756c9bd9f644
@@ -0,0 +1,25 @@
# Agent Note: Production dsh excludes product subagent providers
Status: implemented
English | [中文](2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md)
## Problem
`@deepseek-ai/dsh` receives the `@deepseek-ai/dsh-base` dependency closure. Including the Codex and Claude Code subagent providers there makes every production install download optional product integration code, including the Claude Agent SDK, even when neither integration is used.
## Decision
This decision supersedes the [shared-host placement](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md): `@deepseek-ai/dsh-base` does not depend on or mount the Codex and Claude Code subagent providers. Their packages remain available for Profiles that install and mount them explicitly. Repository examples keep direct development dependencies so their explicit provider configurations continue to resolve.
## Verification
The base bundle test rejects both provider dependencies and configuration rows. Cordis configuration validation requires explicit examples to declare the provider packages they name.
## Alternatives considered
**Keep dormant providers in the base bundle.** Dormant providers start no product processes, but their packages still enter every production npm install.
## Consequences
Installing `@deepseek-ai/dsh` does not download either product provider through the base bundle. Using either integration requires explicit Profile configuration.
@@ -0,0 +1,25 @@
# Agent Note: 生产 dsh 排除产品 subagent 提供方
Status: implemented
[English](2026-08-12-production-dsh-excludes-product-subagent-providers.md) | 中文
## 问题
`@deepseek-ai/dsh` 会获得 `@deepseek-ai/dsh-base` 的依赖闭包。如果 base 包含 Codex 与 Claude Code subagent 提供方,每次生产安装都会下载可选的产品集成代码,包括 Claude Agent SDK,即使用户并未使用任一集成。
## 决策
本决策取代[共享 host 放置决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)`@deepseek-ai/dsh-base` 不依赖也不挂载 Codex 与 Claude Code subagent 提供方。需要这些集成的 Profile 仍可显式安装并挂载对应包。仓库 examples 保留直接开发依赖,使其显式提供方配置可以继续解析。
## 验证
base 组合包测试会拒绝这两个提供方依赖与配置行。Cordis 配置验证要求显式 examples 声明其引用的提供方包。
## 考虑过的替代方案
**在 base 组合包中保留休眠提供方。** 休眠提供方不会启动产品进程,但其包仍会进入每次生产 NPM 安装。
## 后果
安装 `@deepseek-ai/dsh` 时,不会通过 base 组合包下载任一产品提供方。使用任一集成都需要显式 Profile 配置。
-6
View File
@@ -114,10 +114,6 @@ flowchart LR
cfg --> plugin_dsh_base_subagent_spawn_in_process
plugin_dsh_base_subagent_fork_in_process["subagent-fork-in-process<br/>@deepseek-ai/dsh-subagent-fork-in-process"]
cfg --> plugin_dsh_base_subagent_fork_in_process
plugin_dsh_base_subagent_codex["subagent-codex<br/>@deepseek-ai/dsh-subagent-codex"]
cfg --> plugin_dsh_base_subagent_codex
plugin_dsh_base_subagent_claude_code["subagent-claude-code<br/>@deepseek-ai/dsh-subagent-claude-code"]
cfg --> plugin_dsh_base_subagent_claude_code
plugin_dsh_base_tool_subagent_control["tool-subagent-control<br/>@deepseek-ai/dsh-tool-subagent-control"]
cfg --> plugin_dsh_base_tool_subagent_control
plugin_dsh_base_tool_subagent_list_agents["tool-subagent-list-agents<br/>@deepseek-ai/dsh-tool-subagent-control/list-agents"]
@@ -225,8 +221,6 @@ flowchart LR
| `subagent` | `@deepseek-ai/dsh-subagent` |
| `subagent-spawn-in-process` | `@deepseek-ai/dsh-subagent-spawn-in-process` |
| `subagent-fork-in-process` | `@deepseek-ai/dsh-subagent-fork-in-process` |
| `subagent-codex` | `@deepseek-ai/dsh-subagent-codex` |
| `subagent-claude-code` | `@deepseek-ai/dsh-subagent-claude-code` |
| `tool-subagent-control` | `@deepseek-ai/dsh-tool-subagent-control` |
| `tool-subagent-list-agents` | `@deepseek-ai/dsh-tool-subagent-control/list-agents` |
| `tool-subagent` | `@deepseek-ai/dsh-tool-subagent` |
+23 -11
View File
@@ -28,6 +28,7 @@ const BASE_PATCH = join(REPO_ROOT, 'packages/bundle/base/cordis.patch.yml')
const WEB_PATCH = join(REPO_ROOT, 'packages/bundle/web-app/cordis.patch.yml')
/** The installation anchor whose dependency surface the preset module fallback mirrors. */
const INSTALL_ANCHOR = join(REPO_ROOT, 'apps/cli/package.json')
const EXAMPLES_INSTALL_ANCHOR = join(REPO_ROOT, 'examples/package.json')
const MINIMAL_PROMPT = 'You are a helpful software engineer assistant.'
const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
* When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped.
@@ -43,7 +44,11 @@ const MINIMAL_BASH_DESCRIPTION = `Run commands in a bash shell
* touch the network, or write outside the test. Everything that decides an
* agent's capabilities is the real thing, including both shipped presets.
*/
async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promise<Context> {
async function bootWeb(
settingsFile: string,
extra: PatchOptions[] = [],
extraInstallAnchor?: string,
): Promise<Context> {
const storageRoot = join(dirname(settingsFile), 'storages')
const patches: PatchOptions[] = [
...loadOverlayPatches('dsh-test', BASE_PATCH),
@@ -110,6 +115,7 @@ async function bootWeb(settingsFile: string, extra: PatchOptions[] = []): Promis
// them resolvable — the same mechanism, not a test-only shim.
const home = dirname(settingsFile)
healProfilesModuleFallback(INSTALL_ANCHOR, home)
if (extraInstallAnchor !== undefined) healProfilesModuleFallback(extraInstallAnchor, home)
const profileDir = join(home, 'profiles', 'spec')
await mkdir(profileDir, { recursive: true })
const rootConfig = join(profileDir, 'cordis.yml')
@@ -440,17 +446,23 @@ describe('product subagent rows in user presets', () => {
await mkdir(directory, { recursive: true })
await writeFile(join(directory, 'agent.cordis.yml'), composition)
}
productCtx = await bootWeb(settingsFile, [{
id: 'agent-presets',
config: {
default: 'standard',
roots: [
{ path: join(CONFIG_DIR, 'agent-presets'), trust: 'system' },
{ path: userRoot, trust: 'user' },
],
includeUserRoot: false,
productCtx = await bootWeb(settingsFile, [
{ insert: [
{ id: 'subagent-codex', name: '@deepseek-ai/dsh-subagent-codex' },
{ id: 'subagent-claude-code', name: '@deepseek-ai/dsh-subagent-claude-code' },
] },
{
id: 'agent-presets',
config: {
default: 'standard',
roots: [
{ path: join(CONFIG_DIR, 'agent-presets'), trust: 'system' },
{ path: userRoot, trust: 'user' },
],
includeUserRoot: false,
},
},
}])
], EXAMPLES_INSTALL_ANCHOR)
}, 120_000)
afterAll(async () => {
-9
View File
@@ -302,15 +302,6 @@
config:
providerName: fork
# Product providers stay on the host plane because the registry is a
# process singleton. Agent presets decide whether their own model sees the
# matching delegation tools; loading either provider starts no product.
- id: subagent-codex
name: '@deepseek-ai/dsh-subagent-codex'
- id: subagent-claude-code
name: '@deepseek-ai/dsh-subagent-claude-code'
# Continuable background children are selected per delegation tool. The
# separately loaded follow-up tool registers the one global `send_message`.
- id: tool-subagent-control
-2
View File
@@ -85,8 +85,6 @@
"@deepseek-ai/dsh-spill-local": "workspace:^",
"@deepseek-ai/dsh-spill-policy": "workspace:^",
"@deepseek-ai/dsh-subagent": "workspace:^",
"@deepseek-ai/dsh-subagent-claude-code": "workspace:^",
"@deepseek-ai/dsh-subagent-codex": "workspace:^",
"@deepseek-ai/dsh-subagent-fork-in-process": "workspace:^",
"@deepseek-ai/dsh-subagent-spawn-in-process": "workspace:^",
"@deepseek-ai/dsh-subprocess-local": "workspace:^",
+4 -6
View File
@@ -35,12 +35,10 @@ describe('dsh-base bundle', () => {
expect(rows.find(row => row.id === 'session-telemetry-otel')?.config?.['mode']).toEqual({
__jsExpr: "process.env.DSH_TELEMETRY_MODE || 'DISABLED'",
})
expect(rows.filter(row => row.id === 'subagent-codex')).toHaveLength(1)
expect(rows.filter(row => row.id === 'subagent-claude-code')).toHaveLength(1)
expect(manifest.dependencies).toMatchObject({
'@deepseek-ai/dsh-subagent-codex': 'workspace:^',
'@deepseek-ai/dsh-subagent-claude-code': 'workspace:^',
})
expect(rows.filter(row => row.id === 'subagent-codex')).toHaveLength(0)
expect(rows.filter(row => row.id === 'subagent-claude-code')).toHaveLength(0)
expect(manifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subagent-codex')
expect(manifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subagent-claude-code')
})
it('gates each shell stack by platform with a symmetric disabled expression', () => {