mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
fix(subagent): use bundled Claude Code CLI
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-10-product-subagent-providers-in-shared-host.md
|
||||
2026-08-10-product-subagent-providers-in-shared-host.md: 6db6ca665532ec2b457859243fee5cdc750e954b
|
||||
2026-08-10-product-subagent-providers-in-shared-host.zh.md: e6c221299d80e6e66858db607c6b4696942b8a61
|
||||
2026-08-10-product-subagent-providers-in-shared-host.md: 2b1a417f7e751b2edee7e3b23dd439feab1353ea
|
||||
2026-08-10-product-subagent-providers-in-shared-host.zh.md: 2f687b7dc9332680b8aa610f46668f197066c517
|
||||
|
||||
+3
-3
@@ -16,15 +16,15 @@ When installed in a Profile, each product Bundle loads its fixed `codex` or `cla
|
||||
|
||||
The [production-closure decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md) partially supersedes only this note's former default-inclusion choice: the base bundle excludes both providers, and each provider package owns its directly installable Bundle patch. This note continues to own process-wide Host placement whenever a product Bundle is installed. The provider-contract note continues to own each product protocol, result mapping, cancellation, process-tree lifecycle, and evidence tiers. The [Agent Preset architecture](2026-08-03-per-session-agent-presets.md) continues to own the Host/Agent split, preset authoring, and the rule that edits affect only newly composed sessions.
|
||||
|
||||
The providers use products already selected by the host environment. Codex starts `codex` from `PATH`; Claude Code resolves `claude` through the shared subprocess execution world and passes the exact path to the official SDK. Bundle loading does not install a product, create product state, probe a version, test authentication, or add product-specific settings. Missing commands and product failures remain local to the attempted delegation.
|
||||
The providers have different executable owners. Codex starts a host `codex` from `PATH`. The Claude Code Bundle installs its pinned Agent SDK and matching platform CLI; the provider lets that SDK choose the private native executable and passes the command through the shared subprocess owner without consulting or falling back to a host `claude`. Loading either Bundle only registers the provider and creates no product state, probes no version or authentication, and adds no product-specific setting. A missing Codex command or Claude platform payload, authentication failure, and other product failures remain local to the attempted delegation.
|
||||
|
||||
## Verification
|
||||
|
||||
Real composition loads the selected set of no product Bundle, Codex only, Claude Code only, or both, and crosses it with Agent Presets that grant none, either, or both tools. It proves the Host registry equals the installed Bundle set, model-visible tools equal the installed-and-granted intersection, and no product process starts during composition. Preset edit coverage retains generation isolation. Keyless ACP snapshots pin the model-visible tool schemas, while provider tests separately prove native executable resolution, failure, cancellation, and process-tree quiescence.
|
||||
Real composition loads the selected set of no product Bundle, Codex only, Claude Code only, or both, and crosses it with Agent Presets that grant none, either, or both tools. It proves the Host registry equals the installed Bundle set, model-visible tools equal the installed-and-granted intersection, and no product process starts during composition. Preset edit coverage retains generation isolation. Keyless ACP snapshots pin the model-visible tool schemas, while provider tests separately prove host executable resolution for Codex, SDK platform-payload selection without fallback for Claude Code, failure, cancellation, and process-tree quiescence.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep both dormant providers in every base Profile.** This makes every matching Preset row immediately usable, but forces every production installation to carry both provider packages and the Claude Agent SDK even when neither integration is wanted.
|
||||
**Keep both dormant providers in every base Profile.** This makes every matching Preset row immediately usable, but forces every production installation to carry both provider packages, the Claude Agent SDK, and its large platform CLI payload even when neither integration is wanted.
|
||||
|
||||
**Store global or per-Profile product enable switches.** A process switch competes with the Preset as owner of model-visible tools and cannot express two sessions using different combinations. Availability and authentication are deployment facts, not another persisted product state.
|
||||
|
||||
|
||||
+3
-3
@@ -16,15 +16,15 @@ Status: implemented
|
||||
|
||||
[生产依赖闭包决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md)只部分取代本说明先前关于默认包含提供方的选择:base 组合包排除两个提供方,每个提供方包负责其可直接安装的 Bundle patch。本说明继续负责产品 Bundle 安装后进程级的 Host 放置。提供方约定说明继续负责每个产品的协议、结果映射、取消、进程树生命周期与证据层级。[Agent Preset 架构](2026-08-03-per-session-agent-presets.md)继续负责宿主与 agent 的划分、preset 创作,以及改动只影响新组装会话的规则。
|
||||
|
||||
这些提供方使用宿主环境已经选定的产品。Codex 启动 `codex`,该命令从 `PATH` 解析;Claude Code 通过共享的子进程执行世界解析 `claude`,并把确切路径交给官方 SDK。加载 Bundle 不会安装产品、创建产品状态、探测版本、测试身份验证,也不会新增产品专属设置。命令缺失和产品故障仍局限于发生问题的那次委派。
|
||||
两个提供方的可执行文件归属不同。Codex 会启动从 `PATH` 解析出的宿主 `codex`。Claude Code Bundle 会安装锁定的 Agent SDK 与匹配平台 CLI;提供方让 SDK 选择该私有原生可执行文件,再把命令交给共享子进程责任方,既不查询也不回退宿主 `claude`。加载任一 Bundle 只会注册提供方,不会创建产品状态、探测版本或身份验证,也不会新增产品专属设置。Codex 命令缺失、Claude 平台载荷缺失、身份验证失败和其他产品故障仍局限于发生问题的那次委派。
|
||||
|
||||
## 验证
|
||||
|
||||
真实组装会加载未安装产品 Bundle、仅安装 Codex、仅安装 Claude Code 或两者都安装这四种集合,并与不授权工具、仅授权其中一个或同时授权两者的 Agent Preset 完整交叉。测试证明 Host 注册表等于已安装 Bundle 集合,模型可见工具等于已安装且已授权集合的交集,并且组装期间不会启动产品进程。Preset 编辑覆盖继续证明代际隔离。无密钥 ACP(Agent Client Protocol)快照固定模型可见工具 schema,提供方测试则另行证明原生可执行文件解析、失败、取消和进程树完全停稳。
|
||||
真实组装会加载未安装产品 Bundle、仅安装 Codex、仅安装 Claude Code 或两者都安装这四种集合,并与不授权工具、仅授权其中一个或同时授权两者的 Agent Preset 完整交叉。测试证明 Host 注册表等于已安装 Bundle 集合,模型可见工具等于已安装且已授权集合的交集,并且组装期间不会启动产品进程。Preset 编辑覆盖继续证明代际隔离。无密钥 ACP(Agent Client Protocol)快照固定模型可见工具 schema,提供方测试则分别证明 Codex 的宿主可执行文件解析、Claude Code 的 SDK 平台载荷选择与无回退行为,以及失败、取消和进程树完全停稳。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**在每个 base Profile 中保留两个休眠提供方。** 这样每条匹配的 Preset 行都能立即使用,但即使用户不需要任一集成,每次生产安装仍会携带两个提供方包和 Claude Agent SDK。
|
||||
**在每个 base Profile 中保留两个休眠提供方。** 这样每条匹配的 Preset 行都能立即使用,但即使用户不需要任一集成,每次生产安装仍会携带两个提供方包、Claude Agent SDK 及其大型平台 CLI 载荷。
|
||||
|
||||
**存储全局或按 Profile 配置的产品启用开关。** 进程级开关会与 Preset 争夺模型可见工具的责任归属,也无法表示两个会话使用不同组合。可用性与身份验证属于部署事实,并非另一份需要持久化的产品状态。
|
||||
|
||||
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md
|
||||
2026-08-04-claude-code-and-codex-subagent-backends.md: 80dc7ad8488b3ed557361ab3e88948e56763c860
|
||||
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 705757e26bccc55eab0787eb440720a2b5a67601
|
||||
2026-08-04-claude-code-and-codex-subagent-backends.md: f9c7529db664d5b7bebcd77ba69fd974d6cad5b3
|
||||
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 8b0e42507c5a0850d569bff3c9abe962b4a03fe0
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ Codex 0.147.0 speaks the Responses protocol, while DeepSeek's public OpenAI-comp
|
||||
|
||||
## Claude Code provider
|
||||
|
||||
`@deepseek-ai/dsh-subagent-claude-code` registers the fixed `claude-code` provider and invokes `@anthropic-ai/claude-agent-sdk@0.3.220`. Before each run, the provider resolves the fixed `claude` name through the host subprocess execution world and passes that exact path as `pathToClaudeCodeExecutable`; the SDK therefore uses the native product that launched DSH rather than selecting its platform `optionalDependency`. A Windows `.cmd` or `.bat` path crosses `cmd.exe /v:off` as a quoted per-spawn environment expansion, so percent, ampersand, and exclamation path components remain data without changing the shared subprocess contract. The provider uses the official `query()` entrypoint and passes the SDK's `spawnClaudeCodeProcess` arguments, cwd, environment, and forwarded signal to `dsh-subprocess`; its private `SpawnedProcess` adapter exposes only the stream, event, kill, and exit facts the SDK requires.
|
||||
`@deepseek-ai/dsh-subagent-claude-code` registers the fixed `claude-code` provider and invokes `@anthropic-ai/claude-agent-sdk@0.3.220`. The provider omits `pathToClaudeCodeExecutable`, so the SDK selects Claude Code 2.1.220 from the matching OS, CPU, and Linux-libc platform package in its own optional dependency closure. The provider does not resolve or fall back to a host `claude`; an omitted, unsupported, missing, or damaged platform payload fails the first delegation at the SDK startup boundary. The provider uses the official `query()` entrypoint and passes the SDK's native `claude` or `claude.exe` command, arguments, cwd, environment, and forwarded signal from `spawnClaudeCodeProcess` to `dsh-subprocess`; its private `SpawnedProcess` adapter exposes only the stream, event, kill, and exit facts the SDK requires.
|
||||
|
||||
The public configuration contains the same two deployment-owned values as the Codex sibling: an explicit `env` overlay and a positive finite `disposeGraceMs` no greater than the repository's shared `MAX_TIMER_DELAY_MS`. Each run creates its own `AbortController`, sets `persistSession: false`, and disables `AskUserQuestion`. The provider deliberately omits `settingSources`, so the SDK reads the host's normal user, project, and local Claude settings relative to the parent Session cwd. It neither copies nor filters those settings and does not create or modify login state. It supplies no `canUseTool`, elicitation, or dialog callback, so unattended interactions fail through the SDK rather than waiting for a user interface the provider does not own.
|
||||
|
||||
@@ -65,7 +65,7 @@ The Codex evidence pins `@openai/codex@0.147.0` and `codex-cli 0.147.0`. Its rea
|
||||
|
||||
The Codex credentialed e2e registers the production provider, starts the same real app-server, and requests one random nonce through the test-private bridge described above. It fixes the external endpoint and model, stores no credential or request payload, requires exactly one completed upstream response, compares the trimmed product answer byte-for-byte with the nonce, and waits for every managed handle to exit.
|
||||
|
||||
The Claude Code evidence pins Agent SDK 0.3.220 and uses its platform-distributed Claude Code 2.1.220 CLI as the deterministic compatibility fixture, routed through the same native executable-resolution path production uses. Its real-product spec observes the exact `x-api-key`, original task, byte-exact final answer, inherited temporary host-setting marker, process failure, local cancellation, whole-tree exit, and a real Windows batch shim under a path containing percent, ampersand, and exclamation metacharacters. This evidence proves the official SDK/CLI integration path, not compatibility with every independently installed product version. Loader and optional Bundle-composition evidence resolve the selected product packages by name while starting neither product, and the provider suite proves that the SDK receives the executable resolved from the host `PATH`.
|
||||
The Claude Code evidence pins Agent SDK 0.3.220, Claude Code 2.1.220, and the identities and versions of all eight SDK platform packages. Its real-product spec lets the SDK select the installed payload, asserts that the shared subprocess argv begins with that package's native CLI, and observes the exact `x-api-key`, original task, byte-exact final answer, inherited temporary host-setting marker, process failure, local cancellation, and whole-tree exit. Unit coverage proves that production never resolves host `PATH`, omits the executable override, forwards the SDK-selected Windows `claude.exe` without a batch shim, and surfaces the SDK's missing-payload error without host fallback. This evidence proves the pinned official SDK/CLI integration rather than compatibility with independently installed Claude versions. Loader and optional Bundle-composition evidence resolve the selected product packages by name while starting neither product.
|
||||
|
||||
The Claude Code credentialed e2e maps the key and fixed official endpoint only in the provider's in-memory environment, uses the documented `deepseek-v4-pro[1m]` and `deepseek-v4-flash` model variables, and traverses the production provider, official SDK, and real CLI. It compares the trimmed result with a random nonce and proves whole-tree exit without calling the Messages API directly from the test.
|
||||
|
||||
@@ -89,6 +89,6 @@ The project owner's distribution authorization is scoped to the official `@anthr
|
||||
|
||||
Users delegate through two stable foreground tools backed by the official product integrations. Installed providers remain in the process-wide Host and tools remain per Preset under the [shared-host placement decision](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md); optional package availability and default exclusion are owned by the [production-closure decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md). This note's provider lifecycle keeps native settings and behavior while shared services retain the sole ownership of task settlement and process-tree quiescence.
|
||||
|
||||
Every delegation pays for a fresh product process and independent model context, and only final text reaches the parent. Product-native configuration makes behavior depend on the deployment's installed product, account state, and workspace settings. Credentialed e2e runs also spend external API quota and depend on the official DeepSeek endpoint; deterministic protocol, failure, cancellation, and approval coverage remains in the keyless tier. The providers do not resume sessions, stream progress, accept new human interaction, roll back tool or file side effects, or impose a wall-clock timeout.
|
||||
Every delegation pays for a fresh product process and independent model context, and only final text reaches the parent. Codex behavior depends on the deployment's installed CLI and native configuration; Claude Code behavior depends on the Bundle-pinned platform CLI plus native account and workspace settings. Credentialed e2e runs also spend external API quota and depend on the official DeepSeek endpoint; deterministic protocol, failure, cancellation, and approval coverage remains in the keyless tier. The providers do not resume sessions, stream progress, accept new human interaction, roll back tool or file side effects, or impose a wall-clock timeout.
|
||||
|
||||
Compatibility is pinned by package-level unit coverage, keyless real-product loopback tests, credentialed DeepSeek nonce tests, public Loader composition, built-package and NodeNext consumer checks, generated documentation and notices, and the repository CI matrix. A supported product or DeepSeek endpoint/model baseline change must refresh those facts; production performs no separate runtime version probe.
|
||||
|
||||
+3
-3
@@ -47,7 +47,7 @@ Codex 0.147.0 使用 Responses 协议,而 DeepSeek 的公开 OpenAI 兼容端
|
||||
|
||||
## Claude Code 提供方
|
||||
|
||||
`@deepseek-ai/dsh-subagent-claude-code` 注册固定的 `claude-code` 提供方,并调用 `@anthropic-ai/claude-agent-sdk@0.3.220`。每次运行前,提供方经宿主 subprocess 执行世界解析固定名称 `claude`,并把准确路径作为 `pathToClaudeCodeExecutable` 交给 SDK;SDK 因此使用启动 DSH 的原生产品,而不是选择自身的 platform `optionalDependency`。Windows `.cmd` 或 `.bat` 路径会作为带引号、仅供本次 spawn 使用的环境展开值穿过 `cmd.exe /v:off`,因此路径中的百分号、与号和感叹号仍只是数据,且无需改变共享子进程约定。提供方使用官方 `query()` 入口点,并将 SDK 的 `spawnClaudeCodeProcess` 参数、cwd、环境和转发的信号交给 `dsh-subprocess`;其私有 `SpawnedProcess` 适配器只公开 SDK 所需的流、事件、终止和退出事实。
|
||||
`@deepseek-ai/dsh-subagent-claude-code` 注册固定的 `claude-code` 提供方,并调用 `@anthropic-ai/claude-agent-sdk@0.3.220`。提供方会省略 `pathToClaudeCodeExecutable`,因此 SDK 会从自己的 optional dependency 闭包中,按操作系统、CPU 与 Linux libc 选择携带 Claude Code 2.1.220 的匹配平台包。提供方既不会解析也不会回退宿主 `claude`;省略 optional dependency、不受支持的平台,以及缺失或损坏的平台载荷,都会在第一次委派的 SDK 启动边界失败。提供方使用官方 `query()` 入口点,并把 SDK 的 `spawnClaudeCodeProcess` 给出的原生 `claude` 或 `claude.exe` 命令、参数、cwd、环境和转发的信号交给 `dsh-subprocess`;其私有 `SpawnedProcess` 适配器只公开 SDK 所需的流、事件、终止和退出事实。
|
||||
|
||||
公开配置包含与 Codex 兄弟提供方相同、由部署方负责的两个值:显式的 `env` 覆盖项,以及须为正有限值且不得大于仓库共享 `MAX_TIMER_DELAY_MS` 的 `disposeGraceMs`。每次运行都会创建自己的 `AbortController`,设置 `persistSession: false` 并禁用 `AskUserQuestion`。提供方故意省略 `settingSources`,因此 SDK 会相对于父会话 cwd 读取宿主机常规的用户、项目和本地 Claude 设置。它既不复制也不过滤这些设置,也不会创建或修改登录状态。提供方不设置 `canUseTool`、elicitation 或对话回调,因此无人值守交互会经 SDK 失败,而不会等待本提供方不负责的用户界面。
|
||||
|
||||
@@ -65,7 +65,7 @@ Codex 证据锁定 `@openai/codex@0.147.0` 与 `codex-cli 0.147.0`。其真实
|
||||
|
||||
带密钥 Codex e2e 会注册生产提供方,启动同样的真实 app-server,并通过上述测试专用桥接层请求一个随机数。该测试固定外部端点与模型,不存储任何凭据或请求载荷,要求上游恰好完成一次响应,将去除首尾空白后的产品答案与该随机数逐字节比较,并等待所有受管句柄退出。
|
||||
|
||||
Claude Code 证据锁定 Agent SDK 0.3.220,并使用 SDK 按平台分发的 Claude Code 2.1.220 CLI 作为确定性兼容性 fixture(测试前置数据),且该 fixture 经生产环境所用的同一原生可执行文件解析路径运行。其真实产品测试会观测确切的 `x-api-key`、原始任务、逐字节完全一致的最终回答、继承的临时宿主设置标记、进程失败、本地取消、整棵进程树退出,以及位于同时含百分号、与号和感叹号路径中的真实 Windows batch shim。这项证据证明官方 SDK/CLI 集成路径,而不证明它与每个独立安装的产品版本兼容。Loader 与可选 Bundle 组装证据会按名称解析已选择的产品包且不启动产品,provider 测试则证明 SDK 收到由宿主 `PATH` 解析出的可执行文件。
|
||||
Claude Code 证据会锁定 Agent SDK 0.3.220、Claude Code 2.1.220,以及八个 SDK 平台包的身份与版本。真实产品测试会让 SDK 选择已安装载荷,断言共享子进程 argv 以该包的原生 CLI 开头,并观测确切的 `x-api-key`、原始任务、逐字节完全一致的最终回答、继承的临时宿主设置标记、进程失败、本地取消和整棵进程树退出。单元覆盖会证明生产运行从不解析宿主 `PATH`、省略可执行文件覆盖、直接转发 SDK 所选的 Windows `claude.exe` 而不经过 batch shim,并且在载荷缺失时原样暴露 SDK 错误且不回退宿主 CLI。这项证据证明锁定的官方 SDK/CLI 集成,而不证明与独立安装的 Claude 版本兼容。Loader 与可选 Bundle 组装证据会按名称解析已选择的产品包且不启动产品。
|
||||
|
||||
带密钥 Claude Code e2e 仅在提供方的内存环境中映射密钥与固定的官方端点,把模型变量设为文档所示的 `deepseek-v4-pro[1m]` 与 `deepseek-v4-flash`,并实际经过生产提供方、官方 SDK 与真实 CLI。它将去除首尾空白后的结果与一个随机数比较,并证明整棵进程树退出,且测试不会直接调用 Messages API。
|
||||
|
||||
@@ -89,6 +89,6 @@ Claude Code 证据锁定 Agent SDK 0.3.220,并使用 SDK 按平台分发的 Cl
|
||||
|
||||
用户通过官方产品集成支持的两个稳定前台工具进行委派。已安装提供方位于进程级 Host、工具按 Preset 暴露,这些规则由[共享宿主归属决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)负责;可选包可用性与默认排除由[生产依赖闭包决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md)负责。本说明规定的提供方生命周期会保留原生设置与行为,而共享服务继续独占任务结算与进程树完全停稳的责任。
|
||||
|
||||
每次委派都要承担新建产品进程和独立模型上下文的开销,且只有最终文本会到达父级。产品原生配置使行为取决于部署环境中安装的产品、账户状态和工作区设置。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
|
||||
每次委派都要承担新建产品进程和独立模型上下文的开销,且只有最终文本会到达父级。Codex 行为取决于部署环境中安装的 CLI 与原生配置;Claude Code 行为取决于 Bundle 锁定的平台 CLI,以及原生账户和工作区设置。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
|
||||
|
||||
兼容性由包级单元测试覆盖率、无密钥真实产品回环测试、带密钥 DeepSeek 随机数测试、公开 Loader 组合、已构建包与 NodeNext 消费方检查、生成的文档与声明以及仓库 CI 矩阵共同锁定。更改受支持的产品基线或 DeepSeek 端点/模型基线时必须刷新这些事实;生产环境不会另行执行运行时版本探测。
|
||||
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md
|
||||
2026-08-12-production-dsh-excludes-product-subagent-providers.md: 53551ad06ce7b735620d605669ddb5ca2d20aef5
|
||||
2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md: a5ff017e2717dee326e8aaf7987ab8360452902f
|
||||
2026-08-12-production-dsh-excludes-product-subagent-providers.md: b729115ead5ec6823b0c98815fa12f50022defdb
|
||||
2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md: ac10ae2b4f04ddc3997b5fe4bbb8f656742de547
|
||||
|
||||
+4
-4
@@ -6,17 +6,17 @@ English | [中文](2026-08-12-production-dsh-excludes-product-subagent-providers
|
||||
|
||||
## 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.
|
||||
`@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 and its roughly 250 MB unpacked platform CLI payload, even when neither integration is used.
|
||||
|
||||
## Decision
|
||||
|
||||
This decision partially supersedes only the default-inclusion part of 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. Each existing provider package is instead a directly installable Profile Bundle whose `dsh.bundle.patch` points to one package-owned `cordis.patch.yml`. That patch contributes exactly one self-provider Host row and no Agent tool row.
|
||||
|
||||
The two Bundles remain independent. The Codex Bundle owns its `@deepseek-ai/dsh-sdk-protocol` runtime dependency; the Claude Code Bundle owns its Agent SDK runtime dependency. Installing one does not pull in the other, and the default `@deepseek-ai/dsh` production closure contains neither provider nor the Claude Agent SDK. An installed Bundle registers a dormant provider on the next Profile start, while an Agent Preset independently decides whether a new Session receives the corresponding tool. Installation does not start, authenticate, configure, or grant model access to either product.
|
||||
The two Bundles remain independent. The Codex Bundle owns its `@deepseek-ai/dsh-sdk-protocol` runtime dependency and continues to use a host `codex` from `PATH`. The Claude Code Bundle owns the pinned Agent SDK and the matching platform CLI selected from the SDK's optional dependencies; production uses that private CLI and never falls back to a host `claude`. Installing one Bundle does not pull in the other, and the default `@deepseek-ai/dsh` production closure contains neither provider, the Claude Agent SDK, nor its platform payloads. An installed Bundle registers a dormant provider on the next Profile start, while an Agent Preset independently decides whether a new Session receives the corresponding tool. Installation brings only the selected package closure onto disk; it does not start a product, authenticate an account, rewrite native settings, or grant model access.
|
||||
|
||||
## Verification
|
||||
|
||||
Package tests pin each Bundle manifest, published patch, exact self-provider row, and product-specific runtime dependency. Workspace validation derives each published patch from its Bundle declaration rather than a package catalog. Production-closure tests prove the default, Codex-only, and Claude-only dependency boundaries, while real Bundle-patch and Agent-Preset composition covers all four installed sets, the full tool-grant matrix on a Host with both providers, representative missing-provider cases, and zero product processes. The base bundle test continues to reject both provider dependencies and configuration rows.
|
||||
Package tests pin each Bundle manifest, published patch, exact self-provider row, and product-specific runtime closure. Claude coverage pins Agent SDK 0.3.220, Claude Code 2.1.220, all eight platform package identities and versions, the SDK-selected executable entering the shared subprocess owner, and first-delegation failure without host fallback when the payload is missing. Workspace validation derives each published patch from its Bundle declaration rather than a package catalog. Production-closure tests prove the default, Codex-only, and Claude-only dependency boundaries, while real Bundle-patch and Agent-Preset composition covers all four installed sets, the full tool-grant matrix on a Host with both providers, representative missing-provider cases, and zero product processes. The base bundle test continues to reject both provider dependencies and configuration rows.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,4 +26,4 @@ Package tests pin each Bundle manifest, published patch, exact self-provider row
|
||||
|
||||
## Consequences
|
||||
|
||||
Installing `@deepseek-ai/dsh` does not download either product provider through the base bundle. A Profile can add or remove either provider package, or both, directly; the changed Host availability takes effect on the next Profile start. A separately authored Agent Preset still grants the model-visible tool only to newly composed Sessions. No wrapper package, meta Bundle, dynamic installer, or persisted product-enable state is introduced.
|
||||
Installing `@deepseek-ai/dsh` does not download either product provider through the base bundle. A Profile can add or remove either provider package, or both, directly; the changed Host availability takes effect on the next Profile start. Selecting Claude Code explicitly accepts its SDK and one large platform CLI payload, while selecting Codex does not install a product CLI. A separately authored Agent Preset still grants the model-visible tool only to newly composed Sessions. No wrapper package, meta Bundle, dynamic installer, or persisted product-enable state is introduced.
|
||||
|
||||
+4
-4
@@ -6,17 +6,17 @@ Status: implemented
|
||||
|
||||
## 问题
|
||||
|
||||
`@deepseek-ai/dsh` 会获得 `@deepseek-ai/dsh-base` 的依赖闭包。如果 base 包含 Codex 与 Claude Code subagent 提供方,每次生产安装都会下载可选的产品集成代码,包括 Claude Agent SDK,即使用户并未使用任一集成。
|
||||
`@deepseek-ai/dsh` 会获得 `@deepseek-ai/dsh-base` 的依赖闭包。如果 base 包含 Codex 与 Claude Code subagent 提供方,每次生产安装都会下载可选的产品集成代码,包括 Claude Agent SDK 及其解包后约 250 MB 的平台 CLI 载荷,即使用户并未使用任一集成。
|
||||
|
||||
## 决策
|
||||
|
||||
本决策只部分取代[共享 host 放置决策](../architecture/2026-08-10-product-subagent-providers-in-shared-host.md)中关于默认包含提供方的部分:`@deepseek-ai/dsh-base` 不依赖也不挂载 Codex 与 Claude Code subagent 提供方。现有的每个提供方包改为可直接安装的 Profile Bundle,其 `dsh.bundle.patch` 指向包自身拥有的 `cordis.patch.yml`。该 patch 恰好贡献一条挂载自身提供方的 Host 行,不包含 Agent 工具行。
|
||||
|
||||
两个 Bundle 彼此独立。Codex Bundle 自己负责运行时依赖 `@deepseek-ai/dsh-sdk-protocol`;Claude Code Bundle 自己负责 Agent SDK 运行时依赖。安装其中一个不会带入另一个,默认的 `@deepseek-ai/dsh` 生产依赖闭包既不包含任一提供方,也不包含 Claude Agent SDK。已安装的 Bundle 会在下次 Profile 启动时注册一个休眠提供方,而 Agent Preset 独立决定新 Session 是否获得对应工具。安装不会启动产品、验证身份、配置产品或向模型授予任一产品的访问权。
|
||||
两个 Bundle 彼此独立。Codex Bundle 自己负责运行时依赖 `@deepseek-ai/dsh-sdk-protocol`,并继续使用 `PATH` 中的宿主 `codex`。Claude Code Bundle 自己负责锁定的 Agent SDK,以及从 SDK optional dependencies 中选出的匹配平台 CLI;生产运行只使用该私有 CLI,绝不会回退到宿主 `claude`。安装其中一个 Bundle 不会带入另一个,默认的 `@deepseek-ai/dsh` 生产依赖闭包既不包含任一提供方,也不包含 Claude Agent SDK 或其平台载荷。已安装的 Bundle 会在下次 Profile 启动时注册一个休眠提供方,而 Agent Preset 独立决定新 Session 是否获得对应工具。安装只会把所选包闭包放到磁盘上;它不会启动产品、验证账户、改写原生设置或向模型授予访问权。
|
||||
|
||||
## 验证
|
||||
|
||||
包测试会固定每个 Bundle 的 manifest、发布 patch、准确的自身提供方行以及产品专属运行时依赖。工作区验证会从 Bundle 声明派生每个发布 patch,而非维护包目录。生产闭包测试证明默认、仅 Codex 与仅 Claude 三种依赖边界;真实 Bundle patch 与 Agent Preset 的组装会覆盖四种安装集合、同时安装两个提供方时的完整工具授权矩阵、缺失提供方的代表场景以及零产品进程。base 组合包测试仍会拒绝这两个提供方依赖与配置行。
|
||||
包测试会固定每个 Bundle 的 manifest、发布 patch、准确的自身提供方行以及产品专属运行时闭包。Claude 覆盖会固定 Agent SDK 0.3.220、Claude Code 2.1.220、八个平台包的身份与版本、SDK 所选可执行文件进入共享子进程责任方的路径,以及载荷缺失时第一次委派失败且不回退宿主 CLI。工作区验证会从 Bundle 声明派生每个发布 patch,而非维护包目录。生产闭包测试证明默认、仅 Codex 与仅 Claude 三种依赖边界;真实 Bundle patch 与 Agent Preset 的组装会覆盖四种安装集合、同时安装两个提供方时的完整工具授权矩阵、缺失提供方的代表场景以及零产品进程。base 组合包测试仍会拒绝这两个提供方依赖与配置行。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
@@ -26,4 +26,4 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
安装 `@deepseek-ai/dsh` 时,不会通过 base 组合包下载任一产品提供方。Profile 可以直接添加或移除任一提供方包,也可以同时操作两者;Host 可用性的变化会在下次 Profile 启动时生效。单独创作的 Agent Preset 仍只会向新组装的 Session 授予模型可见工具。本决策不引入 wrapper 包、meta Bundle、动态安装程序或持久化的产品启用状态。
|
||||
安装 `@deepseek-ai/dsh` 时,不会通过 base 组合包下载任一产品提供方。Profile 可以直接添加或移除任一提供方包,也可以同时操作两者;Host 可用性的变化会在下次 Profile 启动时生效。选择 Claude Code 代表明确接受其 SDK 与一个大型平台 CLI 载荷,而选择 Codex 不会安装产品 CLI。单独创作的 Agent Preset 仍只会向新组装的 Session 授予模型可见工具。本决策不引入 wrapper 包、meta Bundle、动态安装程序或持久化的产品启用状态。
|
||||
|
||||
@@ -154,7 +154,7 @@ Copy these disabled templates from a shipped full preset and remove `disabled` o
|
||||
maxDepth: provider-managed
|
||||
```
|
||||
|
||||
The two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only an installed matching provider, and enabling both exposes the installed intersection. The host must already provide `codex` or `claude` on `PATH`; neither the Bundle nor the preset starts, authenticates, selects a model for, probes, or manages a host-level installation of either product. The Claude Code Bundle's Agent SDK dependency still carries its platform CLI payload, which production ignores in favor of the host's `claude`.
|
||||
The two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only an installed matching provider, and enabling both exposes the installed intersection. The host must provide `codex` on `PATH` for the Codex provider. The Claude Code Bundle installs and exclusively uses the matching platform CLI selected by its pinned Agent SDK; it does not inspect or fall back to a host `claude`, and a missing optional payload fails the first delegation. Neither Bundle nor the preset starts a product during composition, authenticates an account, selects a model, probes credentials, or manages native product settings.
|
||||
|
||||
## What not to move into a preset
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
|
||||
README.md: dd4e9801452d6c80dc7054b17c70397536adc05c
|
||||
README.zh.md: d029f0f42e2008eddc5ff07177f8eb6ab11fcbba
|
||||
README.md: 4bb0502a9af9299fed991bb8a2279d74ccf97037
|
||||
README.zh.md: 1b641cefaa4f5ece496fc6af2447e45cd308f395
|
||||
|
||||
@@ -52,7 +52,7 @@ dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-codex
|
||||
dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-claude-code
|
||||
```
|
||||
|
||||
The successful pnpm operation changes the Profile manifest and Bundle list on disk; a running Profile keeps the Bundle set from its current start. Restart that Profile after adding, removing, or updating a Bundle. This startup boundary applies to Bundle membership, while ordinary edits to the Profile or home `cordis.patch.yml` retain their existing hot-reload behavior. On the next start, each installed product Bundle registers only its dormant Host provider and does not start, authenticate, configure, or manage a host-level installation of the native product. The Claude Code Bundle's Agent SDK dependency still carries its platform CLI payload, which production ignores in favor of the host's `claude`. Full Agent Presets keep both product tool rows disabled, so a copied Preset must separately enable the matching row before a new Agent can see that tool. Installing one provider never installs the other product package; the default dsh dependency closure includes neither provider nor the Claude Agent SDK.
|
||||
The successful pnpm operation changes the Profile manifest and Bundle list on disk; a running Profile keeps the Bundle set from its current start. Restart that Profile after adding, removing, or updating a Bundle. This startup boundary applies to Bundle membership, while ordinary edits to the Profile or home `cordis.patch.yml` retain their existing hot-reload behavior. On the next start, each installed product Bundle registers only its dormant Host provider and starts no product process. The Codex provider resolves a host `codex` from `PATH`; the Claude Code Bundle instead installs the pinned Agent SDK and one matching private platform CLI, uses only that CLI, and never falls back to a host `claude`. Authentication and native product settings remain user-managed for both products; the [Claude Code provider README](../../../packages/subagent/subagent-claude-code/README.md) discloses the current platform payload size and missing-payload failure. Full Agent Presets keep both product tool rows disabled, so a copied Preset must separately enable the matching row before a new Agent can see that tool. Installing one provider never installs the other product package; the default dsh dependency closure includes neither provider nor the Claude Agent SDK or its platform payloads.
|
||||
|
||||
```sh
|
||||
dsh plugin --profile tui add github:deepseek-harness/turtle-ui
|
||||
|
||||
@@ -52,7 +52,7 @@ dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-codex
|
||||
dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-claude-code
|
||||
```
|
||||
|
||||
pnpm 操作成功后只会改变磁盘上的 Profile manifest 与 Bundle 列表;正在运行的 Profile 会保留本次启动时的 Bundle 集合。添加、移除或更新 Bundle 后须重启该 Profile。这个启动边界只适用于 Bundle 成员变化,Profile 或 home 中普通 `cordis.patch.yml` 的编辑仍保留既有热重载行为。下一次启动时,每个已安装的产品 Bundle 只注册自己的休眠 Host provider,不会启动、认证、配置原生产品,也不会管理宿主级产品安装。Claude Code Bundle 的 Agent SDK 依赖仍携带平台 CLI 载荷,但生产环境会忽略该载荷并使用宿主提供的 `claude`。完整 Agent Preset 中的两个产品工具行仍默认禁用,因此还须在复制出的 Preset 中单独启用对应行,新 Agent 才能看到该工具。只安装一个 provider 不会安装另一个产品包;默认 dsh 依赖闭包不包含任一 provider,也不包含 Claude Agent SDK。
|
||||
pnpm 操作成功后只会改变磁盘上的 Profile manifest 与 Bundle 列表;正在运行的 Profile 会保留本次启动时的 Bundle 集合。添加、移除或更新 Bundle 后须重启该 Profile。这个启动边界只适用于 Bundle 成员变化,Profile 或 home 中普通 `cordis.patch.yml` 的编辑仍保留既有热重载行为。下一次启动时,每个已安装的产品 Bundle 只注册自己的休眠 Host provider,不会启动产品进程。Codex provider 会从 `PATH` 解析宿主 `codex`;Claude Code Bundle 则会安装锁定的 Agent SDK 与一个匹配的私有平台 CLI,只使用该 CLI,并且绝不会回退到宿主 `claude`。两个产品的身份验证与原生设置仍由用户管理;[Claude Code provider README](../../../packages/subagent/subagent-claude-code/README.md)会披露当前平台载荷体积与载荷缺失时的失败行为。完整 Agent Preset 中的两个产品工具行仍默认禁用,因此还须在复制出的 Preset 中单独启用对应行,新 Agent 才能看到该工具。只安装一个 provider 不会安装另一个产品包;默认 dsh 依赖闭包不包含任一 provider,也不包含 Claude Agent SDK 或其平台载荷。
|
||||
|
||||
```sh
|
||||
dsh plugin --profile tui add github:deepseek-harness/turtle-ui
|
||||
|
||||
@@ -17,7 +17,7 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/skill-tool-row', import.
|
||||
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/skill-tool-row/ui.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
const SEED_ID = 'skill-tool-row-web-e2e'
|
||||
const PROMPT = 'Load the snapshot-skill skill with the skill tool, then reply DONE.'
|
||||
const PROMPT = 'Load the editing-cordis-compositions skill with the skill tool, then reply DONE.'
|
||||
|
||||
describe.skipIf(MODE === 'record')('web e2e: dedicated Skill tool row', () => {
|
||||
let scaffold: WebScaffold
|
||||
@@ -53,17 +53,17 @@ describe.skipIf(MODE === 'record')('web e2e: dedicated Skill tool row', () => {
|
||||
it('expands the loaded skill to its exact recorded instructions', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-skill-tool-row'))
|
||||
const call = page.locator('[data-tool="skill"]')
|
||||
const row = call.getByRole('button', { name: 'Skill snapshot-skill' })
|
||||
const row = call.getByRole('button', { name: 'Skill editing-cordis-compositions' })
|
||||
await expect.poll(() => row.getAttribute('aria-expanded')).toBe('false')
|
||||
expect(await call.getByText('snapshot-skill', { exact: true }).count()).toBe(1)
|
||||
expect(await call.getByText('editing-cordis-compositions', { exact: true }).count()).toBe(1)
|
||||
|
||||
await row.click()
|
||||
await expect.poll(() => row.getAttribute('aria-expanded')).toBe('true')
|
||||
await call.getByText('Instructions', { exact: true }).waitFor()
|
||||
const output = call.locator('pre')
|
||||
await output.waitFor()
|
||||
expect(await output.textContent()).toContain('<skill_content name="snapshot-skill">')
|
||||
expect(await output.textContent()).toContain('Follow these snapshot-only instructions.')
|
||||
expect(await output.textContent()).toContain('<skill_content name="editing-cordis-compositions">')
|
||||
expect(await output.textContent()).toContain('The Claude Code Bundle installs and exclusively uses the matching platform CLI')
|
||||
expect(await output.evaluate(element => getComputedStyle(element.parentElement!).maxHeight)).toBe('260px')
|
||||
|
||||
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/subagent/subagent-claude-code/README.md
|
||||
README.md: b1a5c4bb4b9d1c3221c38092d8b0b967888b7746
|
||||
README.zh.md: b17b78f03f47b35409e211d814dce444835058bd
|
||||
README.md: 058e72a46cd6fb7c15779f3650e191535a8b2571
|
||||
README.zh.md: 05914165c26162de9c0a37cfa7090a8d667e63bc
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
This package registers the fixed `claude-code` subagent provider. Each accepted run invokes the official Claude Agent SDK in the delegating Session's workspace, resolves the native `claude` executable through the shared subprocess service, submits one self-contained text task, and returns only the final answer through the shared [`dsh-subagent`](../subagent/README.md) result contract.
|
||||
This package registers the fixed `claude-code` subagent provider. Each accepted run invokes the official Claude Agent SDK in the delegating Session's workspace, lets the pinned SDK select its installed platform CLI, submits one self-contained text task, and returns only the final answer through the shared [`dsh-subagent`](../subagent/README.md) result contract.
|
||||
|
||||
## Start and ownership
|
||||
|
||||
@@ -29,9 +29,9 @@ The provider advertises no optional start-time capabilities and reports `inherit
|
||||
| `env` | `{}` | Explicit SDK/CLI environment layered over the shared credential-scrubbed parent environment. |
|
||||
| `disposeGraceMs` | `3000` | Positive finite grace in milliseconds, no greater than [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md), between the shared process-tree owner's termination tiers; disposal then waits for whole-tree exit. |
|
||||
|
||||
Production resolves `claude` from the subprocess execution world's credential-scrubbed `PATH`, with explicit `env` entries applied, and passes the resulting path to the SDK as `pathToClaudeCodeExecutable`. On Windows, a resolved `.cmd` or `.bat` path is carried as a quoted, per-spawn environment value that `cmd.exe /v:off` expands once, so valid path metacharacters remain data. The pinned SDK's fixed flags then occupy cmd's command tail and contain no cmd metacharacters; they are not ordinary Windows argv. Native settings and authentication remain authoritative. The plugin does not install another CLI, select a model, create a product home, log in, or probe an account. Credential-shaped ambient variables are removed before the explicit `env` overlay is applied, so an API key or token intended for the child must be supplied there. Non-credential endpoint variables such as `ANTHROPIC_BASE_URL`, along with ordinary ambient values such as `PATH` and `HOME`, remain inherited unless overridden.
|
||||
Production omits `pathToClaudeCodeExecutable`, so Agent SDK 0.3.220 selects the matching native `claude` or `claude.exe` from its own platform package and passes that absolute command through the custom-spawn hook to `dsh-subprocess`. The provider does not inspect `PATH`, implement platform selection, or fall back to a host `claude`. Native settings and authentication remain authoritative. The plugin does not select a model, create a product home, log in, or probe an account. Credential-shaped ambient variables are removed before the explicit `env` overlay is applied, so an API key or token intended for the child must be supplied there. Non-credential endpoint variables such as `ANTHROPIC_BASE_URL`, along with ordinary ambient values such as `PATH` and `HOME`, remain inherited unless overridden; `PATH` does not choose the Claude executable.
|
||||
|
||||
This package is an optional Profile Bundle. Install it into the target Profile, then restart that Profile; its declared `cordis.patch.yml` layer registers only the dormant `claude-code` Host provider and starts no Claude process. Removing the package withdraws that provider on the next Profile start.
|
||||
This package is an optional Profile Bundle. Install it into the target Profile, then restart that Profile; installation brings the pinned Agent SDK and one compatible platform CLI payload into that Profile, while the declared `cordis.patch.yml` layer registers only the dormant `claude-code` Host provider and starts no Claude process. Removing the package withdraws that provider and its private runtime closure on the next Profile start.
|
||||
|
||||
```sh
|
||||
dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-claude-code
|
||||
@@ -63,7 +63,9 @@ Installation controls Host availability, not model permission. Full Agent Preset
|
||||
|
||||
## Product compatibility and evidence
|
||||
|
||||
The runtime dependency is pinned to `@anthropic-ai/claude-agent-sdk@0.3.220`. Production runs the native `claude` installation. The keyless real-product test uses the SDK-distributed Claude Code 2.1.220 CLI as a deterministic fixture, routed through the same native executable-resolution and Windows batch-shim path; it does not claim compatibility with every independently installed version. Loader composition proves that both product packages coexist without starting either product.
|
||||
The runtime dependency is pinned to `@anthropic-ai/claude-agent-sdk@0.3.220`, whose eight platform packages carry Claude Code 2.1.220. A normal install selects one payload for the current OS, CPU, and Linux libc. For the current darwin-arm64 payload, `npm pack --dry-run --json` reports 74,858,812 packed bytes and 256,908,856 unpacked bytes; other platforms may differ, and these values are disclosure rather than an installation threshold. The keyless real-product test runs the SDK-selected CLI against a loopback Messages fixture and asserts that the shared subprocess argv begins with that platform package's native executable. Loader composition proves that both product packages coexist without starting either product.
|
||||
|
||||
Installing with optional dependencies omitted, using an unsupported platform, or losing the selected payload leaves provider registration dormant but makes the first delegation fail with the SDK's native-payload startup error. The provider neither probes a host CLI nor retries with one.
|
||||
|
||||
The project owner's identity-scoped distribution authorization covers the official SDK and the official CLI/platform payloads declared by each SDK version. [`THIRD_PARTY_NOTICES.md`](../../../THIRD_PARTY_NOTICES.md) discloses the current optional payload closure without classifying its declared terms as permissive; unrelated non-permissive runtime dependencies continue to fail the notices gate.
|
||||
|
||||
@@ -73,7 +75,7 @@ The project owner's identity-scoped distribution authorization covers the offici
|
||||
|
||||
#### What the model sees
|
||||
|
||||
The Claude Code child receives the standalone text task as one fresh SDK query. Its workspace is the parent Session cwd, while its model, system instructions, tools, permissions, and authentication come from the host's native Claude settings and product installation.
|
||||
The Claude Code child receives the standalone text task as one fresh SDK query. Its workspace is the parent Session cwd, while its model, system instructions, tools, permissions, and authentication come from native Claude settings; the executable version comes from the Bundle's pinned SDK platform payload.
|
||||
|
||||
#### Token effect
|
||||
|
||||
@@ -101,8 +103,8 @@ Append-only: the new tool result follows the reusable parent request prefix.
|
||||
|
||||
- **One fresh query and process per run** — there is no continuation, resume, pooling, progress stream, or product-session persistence.
|
||||
- **Host settings are intentionally authoritative** — project and user settings can change model, tools, and behavior; the provider does not provide a filtered or hermetic production mode.
|
||||
- **Product installation and account state remain native** — a missing or incompatible `claude`, configuration error, or authentication failure is surfaced as a startup or run error; the plugin provides no installer or login flow.
|
||||
- **The SDK platform CLI remains in the install closure** — production ignores it in favor of the host `claude`, but the current SDK optional dependency is still installed and supplies the keyless compatibility fixture. Removing that payload belongs to the separate product installation-closure follow-up.
|
||||
- **Authentication and account state remain native** — the Bundle supplies the CLI but does not create an account, log in, or rewrite Claude settings; configuration and authentication failures surface as startup or run errors.
|
||||
- **The SDK platform payload is required at delegation time** — installs that omit optional dependencies, unsupported platforms, and missing or damaged payloads fail at the first query; there is no host-CLI fallback.
|
||||
- **No human interaction path** — `AskUserQuestion` is disabled and other interactive callbacks are absent, so tasks requiring new approval or input fail instead of suspending.
|
||||
- **Final text only** — reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain product-local.
|
||||
- **No optional shared capabilities** — output schemas, child personas, tool filtering, and harness depth enforcement are rejected by the shared service for this provider.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
本包(package)注册固定的 `claude-code` subagent 提供方。每次接受运行请求后,它都会在发起委托的会话工作区中调用官方 Claude Agent SDK,通过共享子进程服务解析原生 `claude` 可执行文件,提交一个自包含的文本任务,并通过共享的 [`dsh-subagent`](../subagent/README.md) 结果约定仅返回最终答案。
|
||||
本包(package)注册固定的 `claude-code` subagent 提供方。每次接受运行请求后,它都会在发起委托的会话工作区中调用官方 Claude Agent SDK,让锁定版本的 SDK 选择随包安装的平台 CLI,提交一个自包含的文本任务,并通过共享的 [`dsh-subagent`](../subagent/README.md) 结果约定仅返回最终答案。
|
||||
|
||||
## 启动与所有权
|
||||
|
||||
@@ -29,9 +29,9 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
|
||||
| `env` | `{}` | 显式指定的 SDK/CLI 环境,叠加在由共享机制清除凭证后的父环境之上。 |
|
||||
| `disposeGraceMs` | `3000` | 共享进程树责任方各终止层级之间的宽限期,单位为毫秒且须为正有限值,并不得大于仓库共享的 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.md);随后资源释放会等待整棵进程树退出。 |
|
||||
|
||||
生产环境从子进程执行世界清除凭证后的 `PATH` 解析 `claude`,再应用显式 `env` 条目,并把所得路径作为 `pathToClaudeCodeExecutable` 交给 SDK。在 Windows 上,解析到的 `.cmd` 或 `.bat` 路径会作为带引号、仅供本次 spawn 使用的环境值交给 `cmd.exe /v:off` 展开一次,因此合法路径中的元字符仍只是数据。锁定版本的 SDK 随后把固定命令行选项放在 cmd 的命令尾部;这些选项不含 cmd 元字符,也并不是普通的 Windows argv。原生设置与身份验证继续是权威来源。本插件不安装另一份 CLI、不选择模型、不创建产品主目录、不执行登录,也不探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或 token 必须在该配置中显式提供。除非被覆盖,`ANTHROPIC_BASE_URL` 等非凭证端点变量以及 `PATH` 和 `HOME` 等普通环境变量仍会被继承。
|
||||
生产环境会省略 `pathToClaudeCodeExecutable`,因此 Agent SDK 0.3.220 会从自己的平台包中选择匹配的原生 `claude` 或 `claude.exe`,再通过 custom-spawn 钩子把该绝对命令交给 `dsh-subprocess`。提供方不会检查 `PATH`、重复实现平台选择,也不会回退到宿主 `claude`。原生设置与身份验证继续是权威来源。本插件不选择模型、不创建产品主目录、不执行登录,也不探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或 token 必须在该配置中显式提供。除非被覆盖,`ANTHROPIC_BASE_URL` 等非凭证端点变量以及 `PATH` 和 `HOME` 等普通环境变量仍会被继承;`PATH` 不参与选择 Claude 可执行文件。
|
||||
|
||||
本包是可选的 Profile Bundle。将它安装进目标 Profile 后重启该 Profile;包所声明的 `cordis.patch.yml` 层只注册休眠的 `claude-code` Host provider,不会启动 Claude 进程。移除该包后,下一次 Profile 启动会撤回这一 provider。
|
||||
本包是可选的 Profile Bundle。将它安装进目标 Profile 后重启该 Profile;安装会把锁定的 Agent SDK 与一个兼容的平台 CLI 载荷带入该 Profile,而包所声明的 `cordis.patch.yml` 层只注册休眠的 `claude-code` Host provider,不会启动 Claude 进程。移除该包后,下一次 Profile 启动会撤回这一 provider 及其私有运行时闭包。
|
||||
|
||||
```sh
|
||||
dsh plugin --profile <name> add @deepseek-ai/dsh-subagent-claude-code
|
||||
@@ -63,7 +63,9 @@ dsh --profile <name>
|
||||
|
||||
## 产品兼容性与证据
|
||||
|
||||
运行时依赖精确锁定为 `@anthropic-ai/claude-agent-sdk@0.3.220`。生产运行使用原生 `claude` 安装。无密钥真实产品测试使用由 SDK 分发的 Claude Code 2.1.220 CLI 作为确定性 fixture(测试前置数据),并通过同一套原生可执行文件解析路径与 Windows batch shim 路径运行;这项测试不声称兼容每个独立安装的版本。Loader 组合证明两个产品包能够共存且不会启动任一产品。
|
||||
运行时依赖精确锁定为 `@anthropic-ai/claude-agent-sdk@0.3.220`,其八个平台包都携带 Claude Code 2.1.220。普通安装会按当前操作系统、CPU 及 Linux libc 选择一个载荷。对于当前 darwin-arm64 载荷,`npm pack --dry-run --json` 报告压缩包为 74,858,812 字节、解包后为 256,908,856 字节;其他平台可能不同,这些数值只用于披露而不是安装阈值。无密钥真实产品测试会让 SDK 选择 CLI,通过回环 Messages fixture 运行它,并断言共享子进程 argv 的首项就是该平台包的原生可执行文件。Loader 组合证明两个产品包能够共存且不会启动任一产品。
|
||||
|
||||
如果安装时省略 optional dependencies、当前平台不受支持,或所选载荷缺失,提供方注册仍保持休眠,但第一次委派会以 SDK 的原生载荷启动错误失败。提供方既不会探测宿主 CLI,也不会用它重试。
|
||||
|
||||
限定于项目所有者身份的分发授权涵盖官方 SDK 及每个 SDK 版本声明的官方 CLI/平台载荷。[`THIRD_PARTY_NOTICES.md`](../../../THIRD_PARTY_NOTICES.md) 会披露当前可选载荷闭包,但不会认定其中声明的条款属于宽松许可;其他无关的非宽松运行时依赖仍会使第三方声明门禁失败。
|
||||
|
||||
@@ -73,7 +75,7 @@ dsh --profile <name>
|
||||
|
||||
#### 模型看到的内容
|
||||
|
||||
Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。它的工作区是父会话 cwd;其模型、系统指令、工具、权限和身份验证来自宿主机原生 Claude 设置与产品安装。
|
||||
Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。它的工作区是父会话 cwd;其模型、系统指令、工具、权限和身份验证来自原生 Claude 设置,可执行版本则来自 Bundle 锁定的 SDK 平台载荷。
|
||||
|
||||
#### 对 token 的影响
|
||||
|
||||
@@ -101,8 +103,8 @@ Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。
|
||||
|
||||
- **每次运行均新建一个 query 和一个进程**:不支持续接、恢复、池化、进度流或产品会话持久化。
|
||||
- **宿主设置有意保持权威**:项目和用户设置可以改变模型、工具与行为;本提供方不提供经过筛选或与宿主环境隔离的生产模式。
|
||||
- **产品安装与账户状态仍由原生机制管理**:`claude` 缺失或不兼容、配置错误或身份验证失败都会呈现为启动错误或运行错误;本插件不提供安装程序或登录流程。
|
||||
- **SDK 平台 CLI 仍在安装闭包内**:生产环境会忽略它,改用宿主提供的 `claude`,但当前 SDK 的可选依赖仍会安装,并提供无密钥兼容性 fixture。移除该载荷属于独立的产品安装闭包后续项。
|
||||
- **身份验证与账户状态仍由原生机制管理**:Bundle 会提供 CLI,但不会创建账户、登录或改写 Claude 设置;配置与身份验证失败会呈现为启动错误或运行错误。
|
||||
- **委派时必须存在 SDK 平台载荷**:省略 optional dependencies 的安装、不受支持的平台以及缺失或损坏的载荷都会在第一次 query 时失败;不会回退到宿主 CLI。
|
||||
- **没有人工交互路径**:`AskUserQuestion` 被禁用,其他交互回调也不存在,因此需要新审批或输入的任务会失败而不会挂起。
|
||||
- **仅返回最终文本**:推理、中间消息、工具通信、用量信息、stderr 和工作区差异仍只保留在产品内部。
|
||||
- **没有可选的共享能力**:对于本提供方,共享服务会拒绝输出 schema、子任务角色设定、工具筛选和 harness 深度强制约束。
|
||||
|
||||
@@ -66,18 +66,12 @@ class ClaudeCodeProvider implements SubagentProvider {
|
||||
'subagent-claude-code: no working directory for the child — delegate from a parent session that has one',
|
||||
)
|
||||
}
|
||||
const executable = await this.ctx.subprocess.resolveExecutable(
|
||||
'claude',
|
||||
this.config.env,
|
||||
request.signal,
|
||||
)
|
||||
const spec: ClaudeCodeRunSpec = {
|
||||
cwd: resolveChildCwd(
|
||||
'subagent-claude-code',
|
||||
undefined,
|
||||
parentCwd,
|
||||
),
|
||||
executable,
|
||||
env: this.config.env,
|
||||
disposeGraceMs: this.config.disposeGraceMs,
|
||||
spawn: spawnSpec => this.ctx.subprocess.spawn(spawnSpec),
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { extname } from 'node:path'
|
||||
import type {
|
||||
SpawnedProcess,
|
||||
SpawnOptions,
|
||||
@@ -17,8 +16,6 @@ import {
|
||||
type SubprocessSpawnSpec,
|
||||
} from '@deepseek-ai/dsh-subprocess'
|
||||
|
||||
const WINDOWS_BATCH_EXECUTABLE_ENV = 'DSH_CLAUDE_CODE_EXECUTABLE'
|
||||
|
||||
function thrown(value: unknown): Error {
|
||||
/* v8 ignore next -- the subprocess seam rejects with Error. */
|
||||
return value instanceof Error ? value : new Error(String(value))
|
||||
@@ -43,33 +40,22 @@ export function sdkEnvironmentOverlay(
|
||||
* Translate one official SDK spawn request to the shared process owner.
|
||||
* @param options - command, arguments, workspace, environment, and forwarded signal from the SDK.
|
||||
* @param graceMs - process-tree termination grace.
|
||||
* @param platform - host platform selecting the Windows batch-shim boundary.
|
||||
* @returns the fully explicit shared subprocess request.
|
||||
* @remarks The batch-shim path quotes only the resolved executable. The pinned SDK
|
||||
* supplies fixed flag arguments without cmd metacharacters; cmd reparses that tail.
|
||||
*/
|
||||
export function claudeSpawnSpec(
|
||||
options: SpawnOptions,
|
||||
graceMs: number,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): SubprocessSpawnSpec {
|
||||
if (options.cwd === undefined || options.cwd.length === 0) {
|
||||
throw new Error('subagent-claude-code: SDK spawn request omitted its workspace')
|
||||
}
|
||||
const extension = extname(options.command).toLowerCase()
|
||||
const batchShim = platform === 'win32' && (extension === '.cmd' || extension === '.bat')
|
||||
const env = sdkEnvironmentOverlay(options.env)
|
||||
const argv = batchShim
|
||||
? ['cmd.exe', '/d', '/v:off', '/s', '/c', `%${WINDOWS_BATCH_EXECUTABLE_ENV}%`, ...options.args]
|
||||
: [options.command, ...options.args]
|
||||
if (batchShim) env[WINDOWS_BATCH_EXECUTABLE_ENV] = `"${options.command}"`
|
||||
return {
|
||||
argv,
|
||||
argv: [options.command, ...options.args],
|
||||
cwd: options.cwd,
|
||||
stdio: { stdin: 'pipe', stdout: 'pipe', stderr: 'inherit' },
|
||||
graceMs,
|
||||
signal: options.signal,
|
||||
env,
|
||||
env: sdkEnvironmentOverlay(options.env),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,6 @@ export const DEFAULT_DISPOSE_GRACE_MS = 3_000
|
||||
export interface ClaudeCodeRunSpec {
|
||||
/** Parent Session workspace supplied to the SDK and real CLI. */
|
||||
readonly cwd: string
|
||||
/** Exact native Claude Code executable resolved from the host PATH. */
|
||||
readonly executable: string
|
||||
/** Explicit deployment/test environment layered after shared scrubbing. */
|
||||
readonly env: Record<string, string>
|
||||
/** Subprocess termination grace passed to the shared process-tree owner. */
|
||||
@@ -182,7 +180,6 @@ export function claudeQueryOptions(
|
||||
return {
|
||||
abortController: controller,
|
||||
cwd: spec.cwd,
|
||||
pathToClaudeCodeExecutable: spec.executable,
|
||||
env: { ...scrubbedParentEnv(), ...spec.env },
|
||||
persistSession: false,
|
||||
disallowedTools: ['AskUserQuestion'],
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
rmSync,
|
||||
} from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { delimiter, dirname, join, resolve } from 'node:path'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
@@ -87,7 +87,6 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)(
|
||||
]) mkdirSync(directory)
|
||||
|
||||
const env = {
|
||||
PATH: `${dirname(claudeBin)}${delimiter}${process.env.PATH ?? ''}`,
|
||||
ANTHROPIC_AUTH_TOKEN: apiKey,
|
||||
ANTHROPIC_BASE_URL: `${deepSeekBaseUrl()}/anthropic`,
|
||||
ANTHROPIC_MODEL: 'deepseek-v4-pro[1m]',
|
||||
|
||||
@@ -3,12 +3,12 @@ import {
|
||||
mkdirSync,
|
||||
mkdtempSync,
|
||||
readFileSync,
|
||||
symlinkSync,
|
||||
realpathSync,
|
||||
writeFileSync,
|
||||
} from 'node:fs'
|
||||
import { rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { delimiter, dirname, join, resolve } from 'node:path'
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { promisify } from 'node:util'
|
||||
import type {
|
||||
@@ -119,7 +119,6 @@ interface RealHarness {
|
||||
readonly parent: Agent
|
||||
readonly workspace: string
|
||||
readonly env: Record<string, string>
|
||||
readonly executable: string
|
||||
}
|
||||
|
||||
async function realHarness(behavior: MessagesBehavior): Promise<{
|
||||
@@ -131,17 +130,9 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
||||
const workspace = join(root, 'workspace')
|
||||
const claudeConfig = join(root, 'claude-config')
|
||||
const xdgConfig = join(root, 'xdg')
|
||||
const nativeBin = join(root, 'native&%literal%!bang!bin')
|
||||
mkdirSync(workspace)
|
||||
mkdirSync(claudeConfig)
|
||||
mkdirSync(xdgConfig)
|
||||
mkdirSync(nativeBin)
|
||||
const executable = join(nativeBin, process.platform === 'win32' ? 'claude.cmd' : 'claude')
|
||||
if (process.platform === 'win32') {
|
||||
writeFileSync(executable, `@echo off\r\n"${claudeBin}" %*\r\n`)
|
||||
} else {
|
||||
symlinkSync(claudeBin, executable)
|
||||
}
|
||||
writeFileSync(
|
||||
join(claudeConfig, 'settings.json'),
|
||||
`${JSON.stringify({ model: settingsModel }, null, 2)}\n`,
|
||||
@@ -149,7 +140,6 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
||||
const fixture = await startMessagesFixture(behavior)
|
||||
fixtures.push(fixture)
|
||||
const env = {
|
||||
PATH: `${nativeBin}${delimiter}${process.env.PATH ?? ''}`,
|
||||
ANTHROPIC_API_KEY: fakeKey,
|
||||
ANTHROPIC_BASE_URL: fixture.baseUrl,
|
||||
CLAUDE_CONFIG_DIR: claudeConfig,
|
||||
@@ -183,7 +173,7 @@ async function realHarness(behavior: MessagesBehavior): Promise<{
|
||||
session: { header: { cwd: workspace } },
|
||||
} as unknown as Agent
|
||||
return {
|
||||
harness: { ctx, handles, spawnSpecs, parent, workspace, env, executable },
|
||||
harness: { ctx, handles, spawnSpecs, parent, workspace, env },
|
||||
fixture,
|
||||
}
|
||||
}
|
||||
@@ -225,7 +215,7 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
|
||||
expect(sdkPackage.version).toBe('0.3.220')
|
||||
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
|
||||
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
|
||||
const version = await execFileAsync(process.platform === 'win32' ? claudeBin : harness.executable, ['--version'], {
|
||||
const version = await execFileAsync(claudeBin, ['--version'], {
|
||||
env: { ...process.env, ...harness.env },
|
||||
})
|
||||
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
|
||||
@@ -242,18 +232,16 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
|
||||
message.type === 'system' && message.subtype === 'init',
|
||||
)
|
||||
expect(initMessage?.claude_code_version).toBe('2.1.220')
|
||||
if (process.platform === 'win32') {
|
||||
expect(harness.spawnSpecs[0]?.argv.slice(0, 6)).toEqual([
|
||||
'cmd.exe', '/d', '/v:off', '/s', '/c', '%DSH_CLAUDE_CODE_EXECUTABLE%',
|
||||
])
|
||||
const batchExecutable = harness.spawnSpecs[0]?.env?.DSH_CLAUDE_CODE_EXECUTABLE
|
||||
expect(batchExecutable?.startsWith('"')).toBe(true)
|
||||
expect(batchExecutable?.endsWith('"')).toBe(true)
|
||||
expect(batchExecutable?.slice(1, -1).toLowerCase())
|
||||
.toBe(harness.executable.toLowerCase())
|
||||
} else {
|
||||
expect(harness.spawnSpecs[0]?.argv[0]).toBe(harness.executable)
|
||||
}
|
||||
const spawnedExecutable = harness.spawnSpecs[0]?.argv[0]
|
||||
expect(spawnedExecutable).toBeDefined()
|
||||
expect(process.platform === 'win32'
|
||||
? realpathSync(spawnedExecutable!).toLowerCase()
|
||||
: realpathSync(spawnedExecutable!))
|
||||
.toBe(process.platform === 'win32'
|
||||
? realpathSync(claudeBin).toLowerCase()
|
||||
: realpathSync(claudeBin))
|
||||
expect(harness.spawnSpecs[0]?.env)
|
||||
.not.toHaveProperty('DSH_CLAUDE_CODE_EXECUTABLE')
|
||||
|
||||
expect(fixture.requests).toHaveLength(1)
|
||||
const recorded = fixture.requests[0]!
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { PassThrough } from 'node:stream'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type {
|
||||
@@ -56,6 +56,19 @@ type QueryFactory = (params: {
|
||||
|
||||
const queryMock = vi.hoisted(() => vi.fn<QueryFactory>())
|
||||
|
||||
const CLAUDE_AGENT_SDK_VERSION = '0.3.220'
|
||||
const CLAUDE_CODE_VERSION = '2.1.220'
|
||||
const CLAUDE_PLATFORM_PACKAGES = [
|
||||
'@anthropic-ai/claude-agent-sdk-darwin-arm64',
|
||||
'@anthropic-ai/claude-agent-sdk-darwin-x64',
|
||||
'@anthropic-ai/claude-agent-sdk-linux-arm64',
|
||||
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl',
|
||||
'@anthropic-ai/claude-agent-sdk-linux-x64',
|
||||
'@anthropic-ai/claude-agent-sdk-linux-x64-musl',
|
||||
'@anthropic-ai/claude-agent-sdk-win32-arm64',
|
||||
'@anthropic-ai/claude-agent-sdk-win32-x64',
|
||||
] as const
|
||||
|
||||
vi.mock('@anthropic-ai/claude-agent-sdk', async importOriginal => ({
|
||||
...await importOriginal<typeof import('@anthropic-ai/claude-agent-sdk')>(),
|
||||
query: queryMock,
|
||||
@@ -252,7 +265,6 @@ function fakeRun(
|
||||
const options: FakeRun['options'] = []
|
||||
const spec: ClaudeCodeRunSpec = {
|
||||
cwd: '/workspace',
|
||||
executable: '/native/claude',
|
||||
env: { ANTHROPIC_API_KEY: 'fake-key' },
|
||||
disposeGraceMs: 5,
|
||||
spawn: (spawnSpec) => {
|
||||
@@ -295,9 +307,41 @@ describe('task admission and package contracts', () => {
|
||||
}
|
||||
expect(manifest.dsh?.bundle?.patch).toBe('./cordis.patch.yml')
|
||||
expect(manifest.files).toContain('cordis.patch.yml')
|
||||
expect(manifest.dependencies).toHaveProperty('@anthropic-ai/claude-agent-sdk')
|
||||
expect(manifest.dependencies).toHaveProperty(
|
||||
'@anthropic-ai/claude-agent-sdk',
|
||||
CLAUDE_AGENT_SDK_VERSION,
|
||||
)
|
||||
expect(manifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subagent-codex')
|
||||
|
||||
const sdkRoot = dirname(fileURLToPath(
|
||||
import.meta.resolve('@anthropic-ai/claude-agent-sdk'),
|
||||
))
|
||||
const sdkManifest = JSON.parse(readFileSync(
|
||||
resolve(sdkRoot, 'package.json'),
|
||||
'utf8',
|
||||
)) as {
|
||||
version: string
|
||||
claudeCodeVersion: string
|
||||
optionalDependencies: Record<string, string>
|
||||
}
|
||||
expect(sdkManifest.version).toBe(CLAUDE_AGENT_SDK_VERSION)
|
||||
expect(sdkManifest.claudeCodeVersion).toBe(CLAUDE_CODE_VERSION)
|
||||
expect(sdkManifest.optionalDependencies).toEqual(Object.fromEntries(
|
||||
CLAUDE_PLATFORM_PACKAGES.map(packageName => [
|
||||
packageName,
|
||||
CLAUDE_AGENT_SDK_VERSION,
|
||||
]),
|
||||
))
|
||||
const lockfile = readFileSync(resolve(root, '../../../pnpm-lock.yaml'), 'utf8')
|
||||
for (const packageName of CLAUDE_PLATFORM_PACKAGES) {
|
||||
expect(lockfile).toContain(
|
||||
` '${packageName}@${CLAUDE_AGENT_SDK_VERSION}':`,
|
||||
)
|
||||
expect(lockfile).toContain(
|
||||
` '${packageName}': ${CLAUDE_AGENT_SDK_VERSION}`,
|
||||
)
|
||||
}
|
||||
|
||||
const parsed = yaml.load(readFileSync(resolve(root, manifest.dsh!.bundle!.patch!), 'utf8'))
|
||||
const rows = Array.isArray(parsed)
|
||||
? (parsed as Array<{ insert?: Array<{ id?: string; name?: string }> }>).flatMap(entry => entry.insert ?? [])
|
||||
@@ -360,7 +404,7 @@ describe('task admission and package contracts', () => {
|
||||
const spawn = vi.spyOn(ctx.subprocess, 'spawn')
|
||||
.mockImplementation(() => child.handle)
|
||||
const resolveExecutable = vi.spyOn(ctx.subprocess, 'resolveExecutable')
|
||||
.mockResolvedValue('/native/claude')
|
||||
.mockResolvedValue('/host/bin/claude')
|
||||
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
|
||||
await ctx.plugin(claudeCode, {
|
||||
env: {
|
||||
@@ -382,10 +426,15 @@ describe('task admission and package contracts', () => {
|
||||
)
|
||||
expect(queryMock).not.toHaveBeenCalled()
|
||||
|
||||
resolveExecutable.mockRejectedValueOnce(new Error('claude missing from PATH'))
|
||||
vi.stubEnv('PATH', '/host/bin')
|
||||
queryMock.mockImplementationOnce(() => {
|
||||
throw new Error(
|
||||
'Native CLI binary for fixture-platform not found. Reinstall @anthropic-ai/claude-agent-sdk without --omit=optional, or set options.pathToClaudeCodeExecutable.',
|
||||
)
|
||||
})
|
||||
await expect(ctx.subagents.start('claude-code', request()))
|
||||
.rejects.toThrow('claude missing from PATH')
|
||||
expect(queryMock).not.toHaveBeenCalled()
|
||||
.rejects.toThrow('Native CLI binary for fixture-platform not found')
|
||||
expect(resolveExecutable).not.toHaveBeenCalled()
|
||||
|
||||
const run = await ctx.subagents.start('claude-code', request())
|
||||
child.settle({ exitCode: 9, signal: null })
|
||||
@@ -397,13 +446,9 @@ describe('task admission and package contracts', () => {
|
||||
expect(warn).toHaveBeenCalledWith(expect.stringContaining(
|
||||
'subagent-claude-code: child run failed (error):',
|
||||
))
|
||||
expect(resolveExecutable).toHaveBeenCalledWith(
|
||||
'claude',
|
||||
expect.objectContaining({ ANTHROPIC_API_KEY: 'provider-fake-key' }),
|
||||
expect.any(AbortSignal),
|
||||
)
|
||||
expect(queryMock.mock.calls[0]?.[0].options.pathToClaudeCodeExecutable)
|
||||
.toBe('/native/claude')
|
||||
expect(resolveExecutable).not.toHaveBeenCalled()
|
||||
expect(queryMock.mock.calls[1]?.[0].options)
|
||||
.not.toHaveProperty('pathToClaudeCodeExecutable')
|
||||
expect(spawn).toHaveBeenCalledWith(expect.objectContaining({
|
||||
cwd: process.cwd(),
|
||||
graceMs: 29,
|
||||
@@ -483,20 +528,17 @@ describe('official spawn projection', () => {
|
||||
)).toThrow('SDK spawn request omitted its workspace')
|
||||
})
|
||||
|
||||
it.each(['cmd', 'bat'])('routes a Windows .%s shim through cmd.exe', (extension) => {
|
||||
const command = String.raw`C:\Program Files\Claude\claude.${extension}`
|
||||
it('forwards the SDK-selected Windows native executable without a batch shim', () => {
|
||||
const command = String.raw`C:\Program Files\Claude\claude.exe`
|
||||
const spec = claudeSpawnSpec(sdkSpawnOptions({
|
||||
command,
|
||||
args: ['--output-format', 'stream-json'],
|
||||
}), 7, 'win32')
|
||||
}), 7)
|
||||
|
||||
expect(spec.argv).toEqual([
|
||||
'cmd.exe', '/d', '/v:off', '/s', '/c', '%DSH_CLAUDE_CODE_EXECUTABLE%',
|
||||
'--output-format', 'stream-json',
|
||||
command, '--output-format', 'stream-json',
|
||||
])
|
||||
expect(spec.env).toEqual(expect.objectContaining({
|
||||
DSH_CLAUDE_CODE_EXECUTABLE: `"${command}"`,
|
||||
}))
|
||||
expect(spec.env).not.toHaveProperty('DSH_CLAUDE_CODE_EXECUTABLE')
|
||||
})
|
||||
|
||||
it('projects streams, exit facts, listeners, and idempotent tree termination', async () => {
|
||||
@@ -566,7 +608,6 @@ describe('query options and result mapping', () => {
|
||||
const captured: SubprocessHandle[] = []
|
||||
const spec: ClaudeCodeRunSpec = {
|
||||
cwd: '/workspace',
|
||||
executable: '/native/claude',
|
||||
env: {
|
||||
HOST_VISIBLE: 'overridden',
|
||||
ANTHROPIC_API_KEY: 'explicit-fake-key',
|
||||
@@ -582,10 +623,10 @@ describe('query options and result mapping', () => {
|
||||
expect(options).toMatchObject({
|
||||
abortController: controller,
|
||||
cwd: '/workspace',
|
||||
pathToClaudeCodeExecutable: '/native/claude',
|
||||
persistSession: false,
|
||||
disallowedTools: ['AskUserQuestion'],
|
||||
})
|
||||
expect(options).not.toHaveProperty('pathToClaudeCodeExecutable')
|
||||
expect(options.env).toMatchObject({
|
||||
HOST_VISIBLE: 'overridden',
|
||||
ANTHROPIC_API_KEY: 'explicit-fake-key',
|
||||
@@ -730,7 +771,6 @@ describe('run publication, cancellation, and settlement', () => {
|
||||
let index = 0
|
||||
const spec: ClaudeCodeRunSpec = {
|
||||
cwd: '/workspace',
|
||||
executable: '/native/claude',
|
||||
env: {},
|
||||
disposeGraceMs: 5,
|
||||
spawn: () => children[index++]!.handle,
|
||||
@@ -781,7 +821,6 @@ describe('run publication, cancellation, and settlement', () => {
|
||||
request(undefined, parentAbort.signal),
|
||||
{
|
||||
cwd: '/workspace',
|
||||
executable: '/native/claude',
|
||||
env: {},
|
||||
disposeGraceMs: 5,
|
||||
spawn: () => child.handle,
|
||||
|
||||
Reference in New Issue
Block a user