Merge commit '80d68a54120fc87e6b354e41562a9fbfd6874e48' into codex/dsh-sdk-dynamic-subagent-routing

This commit is contained in:
pku-xht
2026-08-25 19:38:11 +08:00
41 changed files with 387 additions and 213 deletions
@@ -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: 196e28c1263c4b6d71eaeb59b9ba8457b36f3ff4
2026-08-10-product-subagent-providers-in-shared-host.zh.md: 1451890e1b250a2095e3366c59d6ce0873b55fe9
2026-08-10-product-subagent-providers-in-shared-host.md: eca1d5b6b9b0e46b39c6c2ef382bfc261d014e44
2026-08-10-product-subagent-providers-in-shared-host.zh.md: 374a3ef72e298bb621fd71d306a4fca16c8000b4
@@ -14,9 +14,9 @@ The placement decision must preserve two independent facts. Loading a provider m
Product providers remain process-scoped host-plane registrations. The [production-install exclusion decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md) supersedes only this note's former base-bundle installation choice: production `dsh-base` neither depends on nor mounts them. A Profile that opts in installs the selected provider Bundle; its patch mounts the default instance, and the Profile may mount additional named instances on the host plane. The [named-instance decision](../feature/2026-08-18-product-subagent-named-instances.md) owns each row's registry identity: both products accept multiple unique `providerName` values while preserving `codex` and `claude-code` as their defaults. Loading either plugin only registers a dormant backend; the corresponding Codex or Claude process starts on the first actual delegation call. Agent Presets independently contribute ordinary `dsh-tool-subagent` rows whose `provider` and `toolName` values expose exactly the configured instances needed by one agent without changing the Host registry.
Each provider package owns its directly installable Bundle patch and private product runtime. This note continues to own process-wide Host placement whenever either provider 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.
Each provider package owns its directly installable Bundle patch and private product runtime. This note continues to own process-wide Host placement whenever either provider is installed. The provider-contract note continues to own each product protocol, result mapping, cancellation, process-tree lifecycle, and evidence tiers. The [named-instance decision](../feature/2026-08-18-product-subagent-named-instances.md) owns the optional Claude Code model and other per-instance configuration. 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.
Each Bundle delegates executable selection to its package-owned product runtime: the Codex package runs its declared wrapper, while the Claude Code package lets its pinned Agent SDK select the private native executable. Neither provider consults or falls back to a host product command. Profile loading creates no product state, probes no version or authentication, and may supply each mounted Provider instance's deployment configuration, including the product-specific `permissionMode` values owned by the [non-interactive permissions decision](../feature/2026-08-15-product-subagent-noninteractive-permissions.md), without moving those choices into an Agent Preset or model-facing tool. Missing platform payloads and product failures remain local to the attempted delegation.
Each Bundle delegates executable selection to its package-owned product runtime: the Codex package runs its declared wrapper, while the Claude Code package lets its pinned Agent SDK select the private native executable. Neither provider consults or falls back to a host product command. Profile loading creates no product state, probes no version or authentication, and may supply each mounted Provider instance's deployment configuration, including an optional opaque model where supported and the product-specific `permissionMode` values owned by the [non-interactive permissions decision](../feature/2026-08-15-product-subagent-noninteractive-permissions.md), without moving those choices into an Agent Preset or model-facing tool. Missing platform payloads and product failures remain local to the attempted delegation.
## Verification
@@ -14,9 +14,9 @@ Status: implemented
产品提供方仍是进程级的 host plane(宿主平面)注册。[生产安装排除决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md)只取代本说明原先由 base bundle 安装提供方的选择:生产 `dsh-base` 既不依赖也不挂载它们。选择产品集成的 Profile 会安装目标提供方 Bundle;其 patch 挂载默认实例,而 Profile 可以在 host plane 挂载更多命名实例。[命名实例决策](../feature/2026-08-18-product-subagent-named-instances.zh.md)负责每个配置项的注册身份:两个产品都接受多个唯一的 `providerName`,同时保留 `codex``claude-code` 作为默认值。加载任一插件只会注册一个休眠后端;对应的 Codex 或 Claude 进程直到第一次实际委派调用时才启动。Agent Preset 通过普通 `dsh-tool-subagent` 配置项的 `provider``toolName` 准确公开单个 agent 所需的已配置实例,而无需更改 Host 注册表。
每个提供方包都拥有可直接安装的 Bundle patch 与私有产品运行时。本说明继续负责每个已安装提供方的进程级 Host 放置。提供方约定说明继续负责每个产品的协议、结果映射、取消、进程树生命周期与证据层级。[Agent Preset 架构](2026-08-03-per-session-agent-presets.zh.md)继续负责宿主与 agent 的划分、preset 创作,以及改动只影响新组装会话的规则。
每个提供方包都拥有可直接安装的 Bundle patch 与私有产品运行时。本说明继续负责每个已安装提供方的进程级 Host 放置。提供方约定说明继续负责每个产品的协议、结果映射、取消、进程树生命周期与证据层级。[命名实例决策](../feature/2026-08-18-product-subagent-named-instances.zh.md)负责可选 Claude Code 模型及其他逐实例配置。[Agent Preset 架构](2026-08-03-per-session-agent-presets.zh.md)继续负责宿主与 agent 的划分、preset 创作,以及改动只影响新组装会话的规则。
每个 Bundle 都把可执行文件选择交给包自有的产品运行时:Codex 包运行自身声明的 wrapperClaude Code 包则让锁定的 Agent SDK 选择私有原生可执行文件。两个提供方都不会查询或回退宿主产品命令。加载 Profile 不会创建产品状态、探测版本或测试身份验证;它可以提供每个已挂载 Provider 实例的部署配置,包括由[非交互权限决策](../feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md)负责的产品专属 `permissionMode` 值,但不会把这些选择移入 Agent Preset 或面向模型的工具。平台载荷缺失和产品故障仍局限于发生问题的那次委派。
每个 Bundle 都把可执行文件选择交给包自有的产品运行时:Codex 包运行自身声明的 wrapperClaude Code 包则让锁定的 Agent SDK 选择私有原生可执行文件。两个提供方都不会查询或回退宿主产品命令。加载 Profile 不会创建产品状态、探测版本或测试身份验证;它可以提供每个已挂载 Provider 实例的部署配置,包括产品支持时可选的不透明模型,以及由[非交互权限决策](../feature/2026-08-15-product-subagent-noninteractive-permissions.zh.md)负责的产品专属 `permissionMode` 值,但不会把这些选择移入 Agent 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 .agents/notes/implemented/feature/2026-08-04-claude-code-and-codex-subagent-backends.md
2026-08-04-claude-code-and-codex-subagent-backends.md: 9b47fcf49d47d2c3561245fa1e16ff8c5da0a35c
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 8ba4d259872558ced89083bb53f9228f46c3d45c
2026-08-04-claude-code-and-codex-subagent-backends.md: 8af99c9171e073e5901390fed29047b350fe4924
2026-08-04-claude-code-and-codex-subagent-backends.zh.md: 5abc0b16077bcf0be7835b2cf687a1709b53fc81
@@ -12,7 +12,7 @@ The product integrations must not become second owners for task text, cwd, cance
## Decision
The harness publishes two sibling one-shot provider packages whose default registry names are `codex` and `claude-code`. This note owns their product protocols, result mapping, and process lifecycle; the [named-instance decision](2026-08-18-product-subagent-named-instances.md) owns Profile-selected provider identity and static tool binding, the [production-install exclusion decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md) owns their independent optional Bundles and host-plane placement, the [product one-shot background decision](2026-08-12-product-subagent-one-shot-background-tasks.md) owns the model-visible scheduling choice, the [non-interactive permissions decision](2026-08-15-product-subagent-noninteractive-permissions.md) owns each product Provider's Profile-selected mode and safe permission decisions, and the [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) owns version-pinned product categories, lifecycle stages, and process outcomes exposed through the same diagnostic. Both packages accept multiple named instances. Loading either provider starts no product process, and each tool accepts only a standalone text task; product and instance selection remain deployment configuration.
The harness publishes two sibling one-shot provider packages whose default registry names are `codex` and `claude-code`. This note owns their product protocols, result mapping, and process lifecycle; the [named-instance decision](2026-08-18-product-subagent-named-instances.md) owns Profile-selected provider identity, optional instance model where supported, and static tool binding; the [production-install exclusion decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md) owns their independent optional Bundles and host-plane placement; the [product one-shot background decision](2026-08-12-product-subagent-one-shot-background-tasks.md) owns the model-visible scheduling choice; the [non-interactive permissions decision](2026-08-15-product-subagent-noninteractive-permissions.md) owns each product Provider's Profile-selected mode and safe permission decisions; and the [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) owns coarse product action categories while the [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) continues to describe Codex's current detailed categories. Both packages accept multiple named instances. Loading either provider starts no product process, and each tool accepts only a standalone text task; product and instance selection remain deployment configuration.
Both providers report `inheritsParentContext: false`, advertise no optional start capabilities, and pass the parent Session cwd without copying the parent conversation. Their documented tools use `backgroundMode: 'one-shot'` and `maxDepth: 'provider-managed'`: the consumer keeps foreground collection as the default and may place the same run in the generic Job runtime, while recursion policy stays with the out-of-process product. Every call creates a fresh product process and a non-resumable product conversation. `ctx.subagents` owns named-request resolution and paired lifecycle events; `dsh-tool-subagent` owns model-visible scheduling and foreground-versus-Job adaptation; `ctx.jobs` and `dsh-tool-jobs` own Job ids, state, output, controls, notices, and parent-owner cancellation; each product provider owns native result mapping, while `dsh-subprocess` owns credential scrubbing, process-tree termination, and whole-tree exit observation.
@@ -48,11 +48,11 @@ Codex 0.147.0 speaks the Responses protocol, while DeepSeek's public OpenAI-comp
## Claude Code provider
`@deepseek-ai/dsh-subagent-claude-code` registers a Profile-selected provider name that defaults to `claude-code` 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.
`@deepseek-ai/dsh-subagent-claude-code` registers a Profile-selected provider name that defaults to `claude-code` and invokes `@anthropic-ai/claude-agent-sdk@0.3.241`. The provider omits `pathToClaudeCodeExecutable`, so the SDK selects Claude Code 2.1.241 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 a non-empty `providerName`, an explicit `env` overlay, a positive finite `disposeGraceMs` no greater than the repository's shared `MAX_TIMER_DELAY_MS`, and a five-value native `permissionMode` that defaults to `dontAsk`. Each named instance retains those resolved values for its own runs. Each run creates its own `AbortController`, sets `persistSession: false`, disables `AskUserQuestion`, and passes the resolved mode to the SDK; only `bypassPermissions` receives the SDK's explicit dangerous confirmation. 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. Remaining permission prompts are denied, MCP elicitation is declined, and blocking dialogs fail closed instead of waiting for a user interface the provider does not own.
The public configuration contains a non-empty `providerName`, an optional non-empty `model`, an explicit `env` overlay, a positive finite `disposeGraceMs` no greater than the repository's shared `MAX_TIMER_DELAY_MS`, and a five-value native `permissionMode` that defaults to `dontAsk`. Each named instance retains those resolved values for its own runs. An explicit model is passed unchanged through `Options.model`; omission leaves that field absent so native settings choose the model. Each run creates its own `AbortController`, sets `persistSession: false`, disables `AskUserQuestion`, and passes the resolved mode to the SDK; only `bypassPermissions` receives the SDK's explicit dangerous confirmation. 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. Remaining permission prompts are denied, MCP elicitation is declined, and blocking dialogs fail closed instead of waiting for a user interface the provider does not own.
The provider publishes only after both the SDK `Query` and a live managed CLI handle exist. It consumes the complete SDK stream and completes only when a `result` message has `subtype: "success"`, `is_error: false`, and a nonblank `result`, and the iterator then ends normally. The [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) owns every non-success category, stage, process outcome, and its ordering with a contributing permission decision. Local cancellation wins and becomes `aborted` without either diagnostic fact.
The provider publishes only after both the SDK `Query` and a live managed CLI handle exist. It consumes the complete SDK stream and completes only when a `result` message has `subtype: "success"`, `is_error: false`, and a nonblank `result`, and the iterator then ends normally. The [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) owns every non-success action category, stage, process outcome, and its ordering with a contributing permission decision. Local cancellation wins and becomes `aborted` without either diagnostic fact.
Startup rollback and published disposal close the SDK query, abort the per-run controller, invoke shared process-tree termination, and wait for whole-tree exit. `Query.close()` expresses graceful protocol intent but does not replace the subprocess owner's exit proof. An unpublished failure exposes only fixed `query-start` facts; a published process failure can expose its independent exit code and signal; an independent cleanup rejection exposes `teardown`. Original SDK, Host, and cleanup errors remain on internal cause chains and logs rather than entering the diagnostic.
@@ -66,7 +66,7 @@ The Codex evidence pins `@openai/codex@0.147.0`, `codex-cli 0.147.0`, and all si
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, Claude Code 2.1.220, and 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, native permission modes, suite-owned denied and bypassed writes, and whole-tree exit. Package tests prove that production never resolves host `PATH`, omits the executable override, and forwards the SDK-selected Windows `claude.exe` without a batch shim. This evidence proves the pinned official SDK/CLI integration rather than compatibility with independently installed Claude versions; the [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) owns failure and process-outcome evidence. Loader coverage resolves both products through their optional Bundle patches while starting neither product.
The Claude Code evidence pins Agent SDK 0.3.241, Claude Code 2.1.241, and 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 omitted-model inheritance, two explicit instance models, the exact `x-api-key`, original task, byte-exact final answer, native permission modes, suite-owned denied and bypassed writes, and whole-tree exit. Package tests prove that production never resolves host `PATH`, omits the executable override, and forwards the SDK-selected Windows `claude.exe` without a batch shim. This evidence proves the pinned official SDK/CLI integration rather than compatibility with independently installed Claude versions; the [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) owns failure and process-outcome evidence. Loader coverage resolves both products through their optional Bundle patches 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.
@@ -82,7 +82,7 @@ The project owner's distribution authorization is scoped to the official `@anthr
**Product doubles as required evidence.** Doubles cover exhaustive private protocol branches but do not prove package exports, official distributions, authentication, or real process behavior. Required evidence drives each official product against a loopback model fixture.
**Plugin-managed login, product home, models, settings, sandbox rules, or fine-grained permission policy.** Those choices would create another authority beside each product's native configuration and enlarge a one-shot provider into account management. Each product exposes only one native non-interactive mode choice in addition to environment and teardown configuration; neither Provider mirrors product rules or adds a human interaction channel.
**Plugin-managed login, product home, model discovery or fallback, settings, sandbox rules, or fine-grained permission policy.** Those choices would create another authority beside each product's native configuration and enlarge a one-shot provider into account management. A Provider may pass one opaque Profile-selected model override where the official product supports it, but it does not discover, validate, alias, or fall back between models. Neither Provider mirrors product rules or adds a human interaction channel.
**Continuation, progress, product-native background state, and shared parent context.** The provider payload remains one final answer for one self-contained task. The generic Job layer may add its id, status, notice, collection, and cancellation results, but product sessions, resume, follow-up, intermediate messages, parent transcript transfer, structured output, and provider-specific background state need separate user contracts and are not prebuilt.
@@ -90,6 +90,6 @@ The project owner's distribution authorization is scoped to the official `@anthr
Users delegate through Profile-configured one-shot tools backed by the official product integrations. Explicit Profile installation and host-plane provider placement are owned by the [production-install exclusion decision](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.md); named instance identity and tool binding are owned by the [named-instance decision](2026-08-18-product-subagent-named-instances.md); per-Preset tool exposure and foreground-default optional Job scheduling are owned by the [product one-shot background decision](2026-08-12-product-subagent-one-shot-background-tasks.md). This note's provider lifecycle keeps native settings and behavior while shared services retain the sole ownership of job settlement and process-tree quiescence.
Every delegation pays for a fresh product process and independent model context. Successful product payload remains final assistant text; a failed product run may separately expose the shared safe diagnostic containing provider-owned permission facts or version-pinned structured failure facts. Background scheduling additionally exposes generic Job ids, status, completion notices, and collection or cancellation results. Both products use Bundle-pinned platform CLIs plus native account and workspace settings and the selected Provider permission mode. 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. Successful product payload remains final assistant text; a failed product run may separately expose the shared safe diagnostic containing provider-owned permission facts and safe product failure categories. Background scheduling additionally exposes generic Job ids, status, completion notices, and collection or cancellation results. Both products use Bundle-pinned platform CLIs plus native account and workspace settings and the selected Provider permission mode; a supported optional instance model overrides only that run's native model selection. 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.
@@ -12,7 +12,7 @@ Status: implemented
## 决策
harness 交付两个同级的一次性提供方包,其默认注册名称分别为 `codex``claude-code`。本说明负责它们的产品协议、结果映射和进程生命周期;[命名实例决策](2026-08-18-product-subagent-named-instances.zh.md)负责 Profile 选择的提供方身份与静态工具绑定[生产安装排除决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md)负责各自独立的可选 Bundle 与 host plane(宿主平面)放置[产品一次性后台任务决策](2026-08-12-product-subagent-one-shot-background-tasks.zh.md)负责模型可见的调度选择[非交互权限决策](2026-08-15-product-subagent-noninteractive-permissions.zh.md)负责各产品提供方的 Profile 模式选择与安全权限决定[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)则负责通过同一诊断公开锁定产品版本的类别、生命周期阶段与进程结果。两个包都接受多个命名实例。加载任一提供方都不会启动产品进程,而且每个工具只接受独立文本任务;产品与实例选择仍属于部署配置。
harness 交付两个同级的一次性提供方包,其默认注册名称分别为 `codex``claude-code`。本说明负责它们的产品协议、结果映射和进程生命周期;[命名实例决策](2026-08-18-product-subagent-named-instances.zh.md)负责 Profile 选择的提供方身份、支持时的可选实例模型与静态工具绑定[生产安装排除决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md)负责各自独立的可选 Bundle 与 host plane(宿主平面)放置[产品一次性后台任务决策](2026-08-12-product-subagent-one-shot-background-tasks.zh.md)负责模型可见的调度选择[非交互权限决策](2026-08-15-product-subagent-noninteractive-permissions.zh.md)负责各产品提供方的 Profile 模式选择与安全权限决定[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责粗粒度产品行动类别,而[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)继续描述 Codex 当前的详细类别。两个包都接受多个命名实例。加载任一提供方都不会启动产品进程,而且每个工具只接受独立文本任务;产品与实例选择仍属于部署配置。
这两个提供方都报告 `inheritsParentContext: false`,不声明任何可选的启动能力,并传递父会话 cwd,但不会复制父级对话。文档所示的工具使用 `backgroundMode: 'one-shot'``maxDepth: 'provider-managed'`:消费方默认在前台收集结果,也可把同一次运行放入通用 Job 运行时,而递归策略仍由进程外产品负责。每次调用都会创建一个全新的产品进程和一次不可续接的产品对话。`ctx.subagents` 负责具名请求解析与成对生命周期事件;`dsh-tool-subagent` 负责模型可见的调度以及前台与 Job 适配;`ctx.jobs``dsh-tool-jobs` 负责 Job id、状态、输出、控制、通知与父级 owner 取消;各产品提供方负责原生结果映射,`dsh-subprocess` 则负责凭证清洗、进程树终止以及整棵进程树的退出观测。
@@ -48,11 +48,11 @@ Codex 0.147.0 使用 Responses 协议,而 DeepSeek 的公开 OpenAI 兼容端
## Claude Code 提供方
`@deepseek-ai/dsh-subagent-claude-code` 注册由 Profile 选择、默认值为 `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 所需的流、事件、终止和退出事实。
`@deepseek-ai/dsh-subagent-claude-code` 注册由 Profile 选择、默认值为 `claude-code` 的提供方名称,并调用 `@anthropic-ai/claude-agent-sdk@0.3.241`。提供方会省略 `pathToClaudeCodeExecutable`,因此 SDK 会从自己的 optional dependency 闭包中,按操作系统、CPU 与 Linux libc 选择携带 Claude Code 2.1.241 的匹配平台包。提供方既不会解析也不会回退宿主 `claude`;省略 optional dependency、不受支持的平台,以及缺失或损坏的平台载荷,都会在第一次委派的 SDK 启动边界失败。提供方使用官方 `query()` 入口点,并把 SDK 的 `spawnClaudeCodeProcess` 给出的原生 `claude``claude.exe` 命令、参数、cwd、环境和转发的信号交给 `dsh-subprocess`;其私有 `SpawnedProcess` 适配器只公开 SDK 所需的流、事件、终止和退出事实。
公开配置包含非空的 `providerName`、显式的 `env` 覆盖项、须为正有限值且不得大于仓库共享 `MAX_TIMER_DELAY_MS``disposeGraceMs`,以及默认使用 `dontAsk` 的五值原生 `permissionMode`。每个命名实例会为自己的运行保留这些已解析值。每次运行都会创建自己的 `AbortController`,设置 `persistSession: false`、禁用 `AskUserQuestion`,并把已解析模式传给 SDK;只有 `bypassPermissions` 会取得 SDK 的显式危险确认。提供方故意省略 `settingSources`,因此 SDK 会相对于父会话 cwd 读取宿主机常规的用户、项目和本地 Claude 设置。它既不复制也不过滤这些设置,也不会创建或修改登录状态。其余权限提示会被拒绝,MCP elicitation 会被拒绝,阻塞对话会快速失败,而不会等待本提供方不负责的用户界面。
公开配置包含非空的 `providerName`可选的非空 `model`显式的 `env` 覆盖项、须为正有限值且不得大于仓库共享 `MAX_TIMER_DELAY_MS``disposeGraceMs`,以及默认使用 `dontAsk` 的五值原生 `permissionMode`。每个命名实例会为自己的运行保留这些已解析值。显式模型会原样传入 `Options.model`;省略时不设置该字段,由原生设置选择模型。每次运行都会创建自己的 `AbortController`,设置 `persistSession: false`、禁用 `AskUserQuestion`,并把已解析模式传给 SDK;只有 `bypassPermissions` 会取得 SDK 的显式危险确认。提供方故意省略 `settingSources`,因此 SDK 会相对于父会话 cwd 读取宿主机常规的用户、项目和本地 Claude 设置。它既不复制也不过滤这些设置,也不会创建或修改登录状态。其余权限提示会被拒绝,MCP elicitation 会被拒绝,阻塞对话会快速失败,而不会等待本提供方不负责的用户界面。
只有在 SDK `Query` 与受管的活动 CLI 句柄都已存在后,提供方才会发布运行。它会消费完整的 SDK 流;只有 `result` 消息具有 `subtype: "success"``is_error: false` 和非空白 `result`,且迭代器随后正常结束时,运行才会完成。[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)负责所有非成功类别、阶段、进程结果,以及它们与参与失败的权限决定之间的顺序。本地取消会胜出并成为 `aborted`,且不附带这两类诊断事实。
只有在 SDK `Query` 与受管的活动 CLI 句柄都已存在后,提供方才会发布运行。它会消费完整的 SDK 流;只有 `result` 消息具有 `subtype: "success"``is_error: false` 和非空白 `result`,且迭代器随后正常结束时,运行才会完成。[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责所有非成功行动类别、阶段、进程结果,以及它们与参与失败的权限决定之间的顺序。本地取消会胜出并成为 `aborted`,且不附带这两类诊断事实。
启动回滚和已发布运行的资源释放都会关闭 SDK query、中止该次运行的控制器、调用共享的进程树终止机制,并等待整棵进程树退出。`Query.close()` 表达优雅的协议关闭意图,但不能取代子进程责任方的退出证明。未发布失败只公开固定的 `query-start` 事实;已发布进程失败可以分别公开退出码与信号;独立清理拒绝则公开 `teardown`。原始 SDK、Host 与清理错误只保留在内部 cause 链和日志中,不进入诊断。
@@ -66,7 +66,7 @@ Codex 证据会锁定 `@openai/codex@0.147.0`、`codex-cli 0.147.0` 与六个平
带密钥 Codex e2e 会注册生产提供方,启动同样的真实 app-server,并通过上述测试专用桥接层请求一个随机数。该测试固定外部端点与模型,不存储任何凭据或请求载荷,要求上游恰好完成一次响应,将去除首尾空白后的产品答案与该随机数逐字节比较,并等待所有受管句柄退出。
Claude Code 证据会锁定 Agent SDK 0.3.220、Claude Code 2.1.220 与八个 SDK 平台包。真实产品测试会让 SDK 选择已安装载荷,断言共享子进程 argv 以该包的原生 CLI 开头,并观测确切的 `x-api-key`、原始任务、逐字节完全一致的最终回答、原生权限模式、测试拥有范围内的拒绝写入与 bypass 写入,以及整棵进程树退出。包测试还会证明生产运行从不解析宿主 `PATH`、省略可执行文件覆盖,并直接转发 SDK 所选的 Windows `claude.exe` 而不经过 batch shim。这项证据证明锁定的官方 SDK/CLI 集成,而不证明与独立安装的 Claude 版本兼容;[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)负责失败与进程结果证据。Loader 覆盖会通过各自的可选 Bundle patch 解析两个产品,且不会启动任一产品。
Claude Code 证据会锁定 Agent SDK 0.3.241、Claude Code 2.1.241 与八个 SDK 平台包。真实产品测试会让 SDK 选择已安装载荷,断言共享子进程 argv 以该包的原生 CLI 开头,并观测省略模型继承、两个显式实例模型、确切的 `x-api-key`、原始任务、逐字节完全一致的最终回答、原生权限模式、测试拥有范围内的拒绝写入与 bypass 写入,以及整棵进程树退出。包测试还会证明生产运行从不解析宿主 `PATH`、省略可执行文件覆盖,并直接转发 SDK 所选的 Windows `claude.exe` 而不经过 batch shim。这项证据证明锁定的官方 SDK/CLI 集成,而不证明与独立安装的 Claude 版本兼容;[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责失败与进程结果证据。Loader 覆盖会通过各自的可选 Bundle patch 解析两个产品,且不会启动任一产品。
带密钥 Claude Code e2e 仅在提供方的内存环境中映射密钥与固定的官方端点,把模型变量设为文档所示的 `deepseek-v4-pro[1m]``deepseek-v4-flash`,并实际经过生产提供方、官方 SDK 与真实 CLI。它将去除首尾空白后的结果与一个随机数比较,并证明整棵进程树退出,且测试不会直接调用 Messages API。
@@ -82,7 +82,7 @@ Claude Code 证据会锁定 Agent SDK 0.3.220、Claude Code 2.1.220 与八个 SD
**以产品替身作为强制证据。** 替身可以穷尽覆盖私有协议分支,但无法证明包导出、官方发行版、身份验证或真实进程行为。强制证据会驱动每个官方产品连接回环模型 fixture。
**由插件管理登录、产品主目录、模型、设置、沙箱规则或细粒度权限策略。** 这些选择会在每个产品的原生配置之外建立另一套权威来源,并将一次性提供方扩张为账户管理功能。两个产品除环境和清理配置外都只公开一个原生非交互模式选择;任一提供方都不会镜像产品规则或增加人工交互通道。
**由插件管理登录、产品主目录、模型发现或 fallback、设置、沙箱规则或细粒度权限策略。** 这些选择会在每个产品的原生配置之外建立另一套权威来源,并将一次性提供方扩张为账户管理功能。官方产品支持时,提供方可以传入一个不透明的 Profile 模型覆盖,但不会发现、校验、解释别名或在模型间 fallback。任一提供方都不会镜像产品规则或增加人工交互通道。
**续接、进度、产品原生后台状态和共享父级上下文。** 提供方载荷仍是一项自包含任务的一个最终回答。通用 Job 层可以额外提供 id、状态、通知、收集与取消结果,但产品会话、恢复、后续交互、中间消息、父级 transcript(文本记录)传递、结构化输出和提供方专属后台状态都需要独立的用户约定,当前实现不会预先构建这些功能。
@@ -90,6 +90,6 @@ Claude Code 证据会锁定 Agent SDK 0.3.220、Claude Code 2.1.220 与八个 SD
用户通过由 Profile 配置、并由官方产品集成支持的一次性工具进行委派。显式 Profile 安装与 host plane 提供方放置由[生产安装排除决策](../simplification/2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md)负责;命名实例身份与工具绑定由[命名实例决策](2026-08-18-product-subagent-named-instances.zh.md)负责;按 Preset 暴露工具以及默认前台且可选通用 Job 的调度方式由[产品一次性后台任务决策](2026-08-12-product-subagent-one-shot-background-tasks.zh.md)负责。本说明规定的提供方生命周期会保留原生设置与行为,而共享服务继续独占作业结算与进程树完全停稳的责任。
每次委派都要承担新建产品进程和独立模型上下文的开销。成功的产品载荷仍只有最终 assistant 文本;失败的产品运行可以另行公开共享安全诊断,其中包含由提供方拥有的权限事实,或锁定版本产品提供的结构化失败事实。后台调度还会额外公开通用 Job id、状态、完成通知以及收集或取消结果。两个产品都使用 Bundle 锁定的平台 CLI,并保留原生账户与工作区设置以及所选提供方权限模式。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
每次委派都要承担新建产品进程和独立模型上下文的开销。成功的产品载荷仍只有最终 assistant 文本;失败的产品运行可以另行公开共享安全诊断,其中包含由提供方拥有的权限事实与安全产品失败类别。后台调度还会额外公开通用 Job id、状态、完成通知以及收集或取消结果。两个产品都使用 Bundle 锁定的平台 CLI,并保留原生账户与工作区设置以及所选提供方权限模式;受支持的可选实例模型只覆盖该次运行的原生模型选择。带密钥 e2e 运行还会消耗外部 API 配额,并依赖 DeepSeek 官方端点;对协议、失败、取消与审批的确定性覆盖仍由无密钥层级承担。提供方不会恢复会话、以流式方式传送进度、接受新的人工交互、回滚工具或文件副作用,也不会施加按实际经过时间触发的超时。
兼容性由包级单元测试覆盖率、无密钥真实产品回环测试、带密钥 DeepSeek 随机数测试、公开 Loader 组合、已构建包与 NodeNext 消费方检查、生成的文档与声明以及仓库 CI 矩阵共同锁定。更改受支持的产品基线或 DeepSeek 端点/模型基线时必须刷新这些事实;生产环境不会另行执行运行时版本探测。
@@ -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-15-product-subagent-noninteractive-permissions.md
2026-08-15-product-subagent-noninteractive-permissions.md: 8788fba3492e08090dd038fc3e7377f6bd1e29cd
2026-08-15-product-subagent-noninteractive-permissions.zh.md: cbf3c3cd14fcecd2c24e335a8b71cc3b5370e247
2026-08-15-product-subagent-noninteractive-permissions.md: f9fe1e0af4f65b83a53127a64f6475861a13f0a1
2026-08-15-product-subagent-noninteractive-permissions.zh.md: 2acb1bf095a23022fc761d254f0e515e1acf1588
@@ -26,7 +26,7 @@ Claude Code defaults to `dontAsk` and accepts only the native non-interactive mo
| `plan` | Use planning mode, deny execution approval, and return the completed plan as the final answer. |
| `bypassPermissions` | Set the SDK's explicit dangerous confirmation and bypass permission checks. |
The Provider continues to omit `settingSources`: Claude Code remains the owner of user, project, and local settings, authentication, tools, and sandbox behavior outside the selected mode.
The Provider continues to omit `settingSources`: an optional instance-level model is a separate direct SDK override, while Claude Code remains the owner of user, project, and local settings, authentication, tools, and sandbox behavior outside the selected mode.
Every query disables `AskUserQuestion`. Non-bypass permission callbacks deny instead of returning the SDK's indefinitely blocking `null`; plan mode also places `ExitPlanMode` in `disallowedTools`, so native allow rules cannot switch the unattended query back to execution. MCP elicitation is declined; the supported refusal dialog is cancelled; undeclared dialog kinds use the SDK's no-dialog failure behavior. A native `permission_denied` message records the same operation-local fact. These paths do not create an approval session, queue, cache, or retry loop.
@@ -44,7 +44,7 @@ The Provider overrides only those thread fields. `CODEX_HOME`, project configura
### Failure diagnostic
`SubagentResult` carries an optional `diagnostic` for provider-authored, non-assistant failure detail. A Provider removes tool inputs, file contents, environment values, credentials, and raw protocol payloads before producing it. The shared out-of-process result boundary limits the complete text to 4096 UTF-8 bytes and marks truncation without splitting a character. The [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) owns non-permission product categories, lifecycle stages, and process outcomes carried by the same field.
`SubagentResult` carries an optional `diagnostic` for provider-authored, non-assistant failure detail. A Provider removes tool inputs, file contents, environment values, credentials, and raw protocol payloads before producing it. The shared out-of-process result boundary limits the complete text to 4096 UTF-8 bytes and marks truncation without splitting a character. The [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) owns Claude Code's non-permission action categories, while the [structured failure-facts decision](2026-08-18-product-subagent-failure-facts.md) continues to own Codex's current categories; both retain lifecycle stages and process outcomes in the same field.
Each product's permission fact contains only the effective mode, request category, unattended decision, and a fixed safe reason. Claude Code derives those facts from SDK callbacks and `permission_denied` messages. Codex derives them from app-server requests, declined items, `sandboxError`, and two fixed permission signatures in a bounded stderr tail; raw stderr is still forwarded to the Host but never copied into the diagnostic. Both Providers place their structured failure line before the latest contributing permission fact. A successful result returns only the strict final answer; local cancellation remains `aborted` without permission detail; an unpublished startup failure still rejects `start()`. The Provider never adds either diagnostic fact to assistant output, structured output, or `subagent/end.lastAssistantMessage`.
@@ -63,7 +63,7 @@ The foreground consumer presents the stop-reason headline, then the optional dia
## Verification
Package tests pin every allowed and rejected Config value, the exact SDK and app-server field mappings, dangerous confirmations, unattended terminal responses, diagnostic sanitization and UTF-8 bound, successful-result omission, concurrent-run isolation, foreground ordering, Job detail, stderr observer disposal, and process cleanup. The real Claude Agent SDK/CLI fixture proves its safe default, restricted denial, explicit bypass, and whole-tree quiescence. The real Codex app-server fixture proves that thread-level `never` overrides ambient `on-request`, automatic review starts, dangerous bypass writes only inside suite-owned temporary storage, fixed stderr signatures produce safe diagnostics, and the wrapper/native tree exits. Loader composition proves non-default modes can be published without starting either product, and the keyless ACP snapshot records each product's failure diagnostic through foreground and Job presentation while the model-facing product tool schemas contain no permission parameter.
Package tests pin every allowed and rejected Config value, the exact SDK and app-server field mappings, dangerous confirmations, unattended terminal responses, diagnostic sanitization and UTF-8 bound, successful-result omission, concurrent-run isolation, foreground ordering, Job detail, stderr observer disposal, and process cleanup. The real Claude Agent SDK 0.3.241 and Claude Code 2.1.241 fixture proves its safe default, restricted denial, explicit bypass, and whole-tree quiescence. The real Codex app-server fixture proves that thread-level `never` overrides ambient `on-request`, automatic review starts, dangerous bypass writes only inside suite-owned temporary storage, fixed stderr signatures produce safe diagnostics, and the wrapper/native tree exits. Loader composition proves non-default modes can be published without starting either product, and the keyless ACP snapshot records each product's failure diagnostic through foreground and Job presentation while the model-facing product tool schemas contain no permission parameter.
## Alternatives considered
@@ -26,7 +26,7 @@ Claude Code 默认使用 `dontAsk`,而且只接受锁定版本 Agent SDK 支
| `plan` | 使用规划模式,拒绝执行审批,并把完整计划作为最终答案返回。 |
| `bypassPermissions` | 设置 SDK 的显式危险确认并跳过权限检查。 |
提供方继续省略 `settingSources`:除所选模式以外,用户、项目和本地设置、身份验证、工具与沙箱行为仍由 Claude Code 拥有。
提供方继续省略 `settingSources`可选的实例级模型是独立的直接 SDK 覆盖;除所选模式以外,用户、项目和本地设置、身份验证、工具与沙箱行为仍由 Claude Code 拥有。
每次 query 都禁用 `AskUserQuestion`。非 bypass 模式的权限回调会拒绝请求,而不会返回 SDK 中会无限阻塞的 `null`plan 模式还会把 `ExitPlanMode` 放入 `disallowedTools`,因此原生 allow 规则无法把无人值守 query 切回执行模式。MCP elicitation 会被拒绝;已支持的拒绝对话会被取消;未声明的对话类型使用 SDK 的无对话失败行为。原生 `permission_denied` 消息会记录同一份当前运行事实。这些路径不会创建审批会话、队列、缓存或重试循环。
@@ -44,7 +44,7 @@ Codex 默认使用 `never`,并接受 Codex 0.147.0 公开的三种原生非交
### 失败诊断
`SubagentResult` 携带可选的 `diagnostic`,用于提供方产生且不属于 assistant 内容的失败说明。提供方在生成它之前会排除工具输入、文件内容、环境值、凭证与原始协议载荷。共享的进程外结果边界会把完整文本限制在 4096 个 UTF-8 字节以内,并在不切断字符的前提下标记截断。[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)负责由同一字段承载的非权限产品类别、生命周期阶段与进程结果。
`SubagentResult` 携带可选的 `diagnostic`,用于提供方产生且不属于 assistant 内容的失败说明。提供方在生成它之前会排除工具输入、文件内容、环境值、凭证与原始协议载荷。共享的进程外结果边界会把完整文本限制在 4096 个 UTF-8 字节以内,并在不切断字符的前提下标记截断。[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责 Claude Code 的非权限行动类别,[结构化失败事实决策](2026-08-18-product-subagent-failure-facts.zh.md)继续负责 Codex 的当前类别;二者都在同一字段中保留生命周期阶段与进程结果。
每个产品的权限事实都只包含有效模式、请求类别、无人值守决定与固定的安全原因。Claude Code 从 SDK 回调和 `permission_denied` 消息取得这些事实。Codex 从 app-server 请求、被拒绝的 item、`sandboxError` 与每次运行有界 stderr 尾部中的两个固定权限签名取得事实;原始 stderr 仍会转发给 Host,但绝不会复制进诊断。两个提供方都会把结构化失败行放在最新参与失败的权限事实之前。成功结果只返回严格的最终答案;本地取消仍以 `aborted` 结算且不附带权限说明;未发布的启动失败仍会拒绝 `start()`。提供方绝不会把任一诊断事实写入 assistant 输出、结构化输出或 `subagent/end.lastAssistantMessage`
@@ -63,7 +63,7 @@ Codex 默认使用 `never`,并接受 Codex 0.147.0 公开的三种原生非交
## Verification
包测试固定所有允许与拒绝的 Config 值、准确的 SDK 与 app-server 字段映射、危险确认、无人值守终态、诊断脱敏与 UTF-8 上限、成功结果不携带诊断、并发运行隔离、前台顺序、Job detail、stderr observer 释放和进程清理。真实 Claude Agent SDK/CLI fixture 证明其安全默认、受限拒绝、显式 bypass 与整棵进程树完全停稳。真实 Codex app-server fixture 证明线程级 `never` 覆盖环境中的 `on-request`、自动评审可以启动、危险绕过只在测试拥有的临时存储中写入、固定 stderr 签名产生安全诊断,而且 wrapper/native 进程树会退出。Loader 组装证明非默认模式可以在不启动任一产品的情况下发布;无密钥 ACP snapshot 则记录每个产品的失败诊断如何经过前台与 Job 呈现,同时面向模型的产品工具 schema 不包含权限参数。
包测试固定所有允许与拒绝的 Config 值、准确的 SDK 与 app-server 字段映射、危险确认、无人值守终态、诊断脱敏与 UTF-8 上限、成功结果不携带诊断、并发运行隔离、前台顺序、Job detail、stderr observer 释放和进程清理。真实 Claude Agent SDK 0.3.241 与 Claude Code 2.1.241 fixture 证明其安全默认、受限拒绝、显式 bypass 与整棵进程树完全停稳。真实 Codex app-server fixture 证明线程级 `never` 覆盖环境中的 `on-request`、自动评审可以启动、危险绕过只在测试拥有的临时存储中写入、固定 stderr 签名产生安全诊断,而且 wrapper/native 进程树会退出。Loader 组装证明非默认模式可以在不启动任一产品的情况下发布;无密钥 ACP snapshot 则记录每个产品的失败诊断如何经过前台与 Job 呈现,同时面向模型的产品工具 schema 不包含权限参数。
## Alternatives considered
@@ -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-18-product-subagent-failure-facts.md
2026-08-18-product-subagent-failure-facts.md: 50d8e918f288a6b8a9b90474499b2ed20731643f
2026-08-18-product-subagent-failure-facts.zh.md: abdbf8c0ebddb1fb30cef3c7e80fcf7040e45d2d
2026-08-18-product-subagent-failure-facts.md: e47fc3246ce20e988cd693b2eb04f785d37bcb27
2026-08-18-product-subagent-failure-facts.zh.md: 5efd15e0e22d38b0100a96fc020ba3671fd257ed
@@ -12,7 +12,7 @@ Copying SDK error text, app-server payloads, or stderr into the result would exp
## Decision
Each product Provider owns the mapping from its pinned official error union, current operation, and managed process outcome to one fixed safe diagnostic line. `SubagentResult` remains unchanged: consumers receive the existing bounded `diagnostic` string and do not parse its product-private fields.
Each product Provider owns the mapping from its pinned official structured failures, current operation, and managed process outcome to one fixed safe diagnostic line. `SubagentResult` remains unchanged: consumers receive the existing bounded `diagnostic` string and do not parse its product-private fields. The [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) supersedes this note's complete Claude Code subtype mirror; this note continues to own the current detailed Codex categories until that provider adopts the same simplification.
### Safe diagnostic
@@ -28,14 +28,7 @@ Successful results and local cancellation expose no failure fact. Raw product er
### Claude Code facts
Agent SDK 0.3.220 defines four error subtypes: `error_during_execution`, `error_max_turns`, `error_max_budget_usd`, and `error_max_structured_output_retries`. The Claude Code Provider preserves each exact subtype as the category while keeping the shared stop reason `error`. An error-marked or blank success uses `invalid-success`, a missing result uses `missing-result`, a process exit before an SDK terminal result uses `process-exit`, and an unrecognized value or exception uses `unknown` without copying the value.
| Stage | Owned operation | Observable failure |
| --- | --- | --- |
| `query-start` | SDK query construction, native platform-payload startup, and unpublished rollback | `start()` rejects with fixed safe facts and any process outcome observed before rollback |
| `query-run` | Published SDK message iteration and strict terminal-result validation | The run resolves as `error` with the exact known subtype or a fixed result category |
| `process` | Managed CLI exits before the SDK supplies a terminal result | The run resolves as `error` with `process-exit` and the available exit code and signal |
| `teardown` | Query close and managed process-tree release | `dispose()` rejects independently with fixed safe facts after cleanup still reaches its final exit wait |
The [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) exclusively owns Claude Code categories, stages, process facts, permission ordering, and verification for Agent SDK 0.3.241 and Claude Code 2.1.241. This note carries no separate Claude category contract.
### Codex facts
@@ -56,7 +49,7 @@ Codex app-server 0.147.0 defines eleven string categories and five object varian
| Fact or resource | Owner | Consumer behavior |
| --- | --- | --- |
| Product error category | Pinned official SDK or app-server version | The Provider maps only the declared structured union and uses `unknown` outside it |
| Codex error category | Codex Provider over its pinned official app-server | The Provider preserves its current structured category and uses `unknown` outside the recognized set |
| Current failure stage | Product Provider operation | Derived at the failure site; never persisted or used as a recovery state |
| Exit code and signal | `dsh-subprocess` process handle | The Provider displays observed values without inferring missing ones |
| Diagnostic bytes and delivery | `dsh-subagent`, foreground tool, and Job runtime | The same bounded text is presented separately from assistant output in both scheduling modes |
@@ -64,7 +57,7 @@ Codex app-server 0.147.0 defines eleven string categories and five object varian
## Verification
Claude Code package tests pin all four SDK subtypes, invalid success, missing result, unknown values and exceptions, all four stages, independent exit code and signal fields, permission-fact ordering, sanitization, successful-result and cancellation omission, concurrent-run isolation, and cleanup completion. Codex package tests pin all sixteen error-info variants, HTTP status presence and absence, all six stages, unknown fallback, stop-reason preservation, permission ordering, sanitization, cancellation, concurrency, and cleanup aggregation. The real SDK/CLI fixture produces an actual Claude `error_max_turns`; the real app-server fixture produces an actual Codex `internalServerError`; both fixtures cover process/protocol failure and whole-tree quiescence. The keyless ACP snapshot records each product's exact diagnostic in foreground error output, a background completion notice, and `job_output`.
Claude Code verification is owned by the [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md). Codex package tests pin all sixteen current error-info variants, HTTP status presence and absence, all six stages, unknown fallback, stop-reason preservation, permission ordering, sanitization, cancellation, concurrency, and cleanup aggregation. The real app-server fixture produces an actual Codex `internalServerError` and covers process/protocol failure and whole-tree quiescence. The keyless ACP snapshot records the Codex diagnostic in foreground error output, a background completion notice, and `job_output`.
## Alternatives considered
@@ -80,8 +73,8 @@ Claude Code package tests pin all four SDK subtypes, invalid success, missing re
## Consequences
The parent can distinguish important Claude Code limits and Codex budget, usage, service, policy, request, connection, stream, rollback, sandbox, and active-turn failures without receiving raw product text. Foreground and background scheduling preserve the same fact because both consume one `SubagentResult`.
The parent can distinguish the current Codex budget, usage, service, policy, request, connection, stream, rollback, sandbox, and active-turn categories without receiving raw product text. The [minimal-diagnostics decision](../simplification/2026-08-21-product-subagent-minimal-diagnostics.md) owns the corresponding Claude result. Foreground and background scheduling preserve the same fact because both consume one `SubagentResult`.
The diagnostic is display text rather than a new public protocol. Callers may present it but must not branch on its punctuation or product-private category names. A pinned product-version upgrade must update the Provider mapping and evidence when its official error union changes.
The diagnostic is display text rather than a new public protocol. Callers may present it but must not branch on its punctuation or product-private category names. A pinned product-version upgrade revalidates the Provider mapping and evidence without requiring every official error member to remain model-visible.
This decision adds no product session persistence, retry policy, recovery state, stderr classifier, authentication or configuration taxonomy, progress stream, or human interaction path.
@@ -12,7 +12,7 @@ Status: implemented
## Decision
每个产品提供方分别拥有从锁定版本官方错误联合、当前操作和受管进程结果到一行固定安全诊断的映射。`SubagentResult` 保持不变:消费方仍接收现有的有界 `diagnostic` 字符串,而且不解析其中由产品私有的字段。
每个产品提供方分别拥有从锁定版本官方结构化失败、当前操作和受管进程结果到一行固定安全诊断的映射。`SubagentResult` 保持不变:消费方仍接收现有的有界 `diagnostic` 字符串,而且不解析其中由产品私有的字段。[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)已经取代本说明对 Claude Code 完整 subtype 的镜像;在 Codex 采用同一简化前,本说明继续负责其当前详细类别。
### 安全诊断
@@ -28,14 +28,7 @@ Product subagent failure (product: <product>; stage: <stage>; category: <categor
### Claude Code 事实
Agent SDK 0.3.220 定义四种错误子类型:`error_during_execution``error_max_turns``error_max_budget_usd``error_max_structured_output_retries`。Claude Code 提供方会把每种准确子类型保留为类别,同时维持共享终止原因 `error`。标记为错误或内容空白的成功消息使用 `invalid-success`,缺失结果使用 `missing-result`,SDK 给出终态结果前发生的进程退出使用 `process-exit`,无法识别的值或异常使用 `unknown`,且不会复制原值
| 阶段 | 归属操作 | 可观察失败 |
| --- | --- | --- |
| `query-start` | SDK query 构造、原生平台载荷启动与未发布回滚 | `start()` 以固定安全事实和回滚前已观测到的进程结果拒绝 |
| `query-run` | 已发布 SDK 消息迭代与严格终态结果校验 | 运行以 `error` 兑现,并携带准确已知子类型或固定结果类别 |
| `process` | SDK 提供终态结果之前受管 CLI 已退出 | 运行以 `error` 兑现,并携带 `process-exit` 以及可用的退出码和信号 |
| `teardown` | Query 关闭与受管进程树释放 | `dispose()` 独立拒绝并携带固定安全事实,同时清理仍会完成最终退出等待 |
[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)独占负责 Agent SDK 0.3.241 与 Claude Code 2.1.241 的 Claude Code 类别、阶段、进程事实、权限顺序与验证。本说明不再承载独立的 Claude 类别约定
### Codex 事实
@@ -56,7 +49,7 @@ Codex app-server 0.147.0 定义十一种字符串类别与五种对象 variant
| 事实或资源 | Owner | 消费方行为 |
| --- | --- | --- |
| 产品错误类别 | 锁定版本的官方 SDK 或 app-server | 提供方只映射已声明的结构化联合,并对联合外值使用 `unknown` |
| Codex 错误类别 | Codex 提供方及其锁定的官方 app-server | 提供方保留当前结构化类别,并在已识别集合之外使用 `unknown` |
| 当前失败阶段 | 产品提供方操作 | 只在失败点派生;绝不持久化,也不作为恢复状态 |
| 退出码与信号 | `dsh-subprocess` 进程句柄 | 提供方展示已观测值,不推测缺失值 |
| 诊断字节与送达 | `dsh-subagent`、前台工具与 Job 运行时 | 两种调度模式都把同一份有界文本与 assistant 输出分开呈现 |
@@ -64,7 +57,7 @@ Codex app-server 0.147.0 定义十一种字符串类别与五种对象 variant
## Verification
Claude Code 包测试固定四种 SDK 子类型、无效成功、缺失结果、未知值与异常、四个阶段、相互独立的退出码与信号字段、权限事实顺序、脱敏、成功结果与取消时省略诊断、并发运行隔离和清理完成。Codex 包测试固定全部十六种 error-info variant、HTTP status 存在与缺失、六个阶段、unknown 回退、终止原因保持不变、权限顺序、脱敏、取消、并发与清理聚合。真实 SDK/CLI fixture 会产生真实的 Claude `error_max_turns`,真实 app-server fixture 会产生真实的 Codex `internalServerError`;两个 fixture 都覆盖进程/协议失败与整棵进程树完全停稳。无密钥 ACP snapshot 会在前台错误输出、后台完成通知和 `job_output` 中记录两个产品各自的准确诊断。
Claude Code 验证由[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责。Codex 包测试固定当前全部十六种 error-info variant、HTTP status 存在与缺失、六个阶段、unknown 回退、终止原因保持不变、权限顺序、脱敏、取消、并发与清理聚合。真实 app-server fixture 会产生实际 Codex `internalServerError`,并覆盖进程/协议失败与整棵进程树完全停稳。无密钥 ACP snapshot 会在前台错误输出、后台完成通知和 `job_output` 中记录 Codex 诊断。
## Alternatives considered
@@ -80,8 +73,8 @@ Claude Code 包测试固定四种 SDK 子类型、无效成功、缺失结果、
## Consequences
父 agent 可以区分重要的 Claude Code 限制,以及 Codex 预算、用量、服务、策略、请求、连接、stream、回滚、sandbox active-turn 失败,而不会收到原始产品文本。前台与后台调度会保留同一事实,因为二者都消费同一个 `SubagentResult`
父 agent 可以区分当前 Codex 预算、用量、服务、策略、请求、连接、stream、回滚、sandbox active-turn 类别,而不会收到原始产品文本。[最小诊断决策](../simplification/2026-08-21-product-subagent-minimal-diagnostics.zh.md)负责对应的 Claude 结果。前台与后台调度会保留同一事实,因为二者都消费同一个 `SubagentResult`
诊断只是展示文本,不是新的公开协议。调用方可以呈现它,但不得根据其标点或产品私有类别名称进行分支。锁定产品版本升级并改变官方错误联合时,必须同步更新提供方映射与证据
诊断只是展示文本,不是新的公开协议。调用方可以呈现它,但不得根据其标点或产品私有类别名称进行分支。锁定产品版本升级时必须重新验证提供方映射与证据,但不要求每个官方错误成员都继续模型可见
本决策不增加产品会话持久化、重试策略、恢复状态、stderr 分类器、身份验证或配置分类体系、进度流或人工交互路径。
@@ -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-18-product-subagent-named-instances.md
2026-08-18-product-subagent-named-instances.md: 759d3941ff8404138954c409f0fd4949e357200e
2026-08-18-product-subagent-named-instances.zh.md: 6faf0e70f639cbc6528e27b800b8e5f99f0d6c86
2026-08-18-product-subagent-named-instances.md: fd4f95dc9de0cc03a95f8f1dcbd6dcded258dc5f
2026-08-18-product-subagent-named-instances.zh.md: 1d8395a21824501c5f26bdbec9c9d7f25d3bf20f
@@ -6,17 +6,17 @@ English | [中文](2026-08-18-product-subagent-named-instances.zh.md)
## Problem
A Profile can mount one Cordis plugin package in multiple rows, but the Codex and Claude Code product providers previously registered every row under one fixed product name. A second row therefore failed as a duplicate before its distinct permission mode, environment, or process-release settings could become usable. Deriving an implicit name from those settings would create a second identity rule, while choosing a provider during a tool call would let model input select deployment authority.
A Profile can mount one Cordis plugin package in multiple rows, but the Codex and Claude Code product providers previously registered every row under one fixed product name. A second row therefore failed as a duplicate before its distinct model where supported, permission mode, environment, or process-release settings could become usable. Deriving an implicit name from those settings would create a second identity rule, while choosing a provider during a tool call would let model input select deployment authority.
The existing subagent registry already owns unique provider names, reversible registration, lifecycle events, and holder-owned published runs. The existing `dsh-tool-subagent` configuration already binds one provider name to one model-visible tool name. Product providers need to expose the missing Profile-owned identity without adding another registry or selection protocol.
## Decision
Each product provider Config owns a non-empty `providerName`; the defaults remain `codex` and `claude-code`. The resolved name is fixed when the plugin row loads and becomes the Provider object's `name`; registration, lookup, lifecycle events, run logs, and HMR removal therefore use the same value. Each mounted row retains its own `permissionMode`, `env`, `disposeGraceMs`, and run resources.
Each product provider Config owns a non-empty `providerName`; the defaults remain `codex` and `claude-code`. The resolved name is fixed when the plugin row loads and becomes the Provider object's `name`; registration, lookup, lifecycle events, run logs, and HMR removal therefore use the same value. Each mounted row retains its own `permissionMode`, `env`, `disposeGraceMs`, and run resources. Claude Code rows also retain an optional non-empty `model`; an explicit value is fixed for every query from that instance, while omission leaves native settings authoritative.
Profiles may mount multiple Codex or Claude Code rows when every row uses a distinct `providerName`. Each `dsh-tool-subagent` row continues to bind its existing `provider` field to that exact name and exposes an independently configured `toolName`. Tool calls carry no provider selector, alias, or permission input. A duplicate provider name fails through the existing `DUPLICATE_PROVIDER` path and leaves the first registration intact.
Removing one provider row blocks new starts and removes only tools bound to that name. Runs already published by the removed instance remain owned by their holders and settle or dispose independently. Sibling instances remain registered and keep their own environment, native permission mode, cancellation controller, product process, and cleanup grace.
Removing one provider row blocks new starts and removes only tools bound to that name. Runs already published by the removed instance remain owned by their holders and settle or dispose independently. Sibling instances remain registered and keep their own configured model where supported, environment, native permission mode, cancellation controller, product process, and cleanup grace.
### Ownership and lifecycle
@@ -25,11 +25,11 @@ Removing one provider row blocks new starts and removes only tools bound to that
| Provider instance name | Product Provider Config | One immutable registry name per mounted row, with the existing default when omitted |
| Name uniqueness and lifecycle events | `ctx.subagents` | Duplicate registration fails; disposal removes only the matching name |
| Model-visible tool name and binding | `dsh-tool-subagent` Config | One static tool resolves one configured provider name |
| Permission, environment, and process cleanup | One Provider instance | Concurrent runs and sibling instances do not share deployment configuration or run resources |
| Model where supported, permission, environment, and process cleanup | One Provider instance | Concurrent runs and sibling instances do not share deployment configuration or run resources |
## Verification
Both product packages pin their default and custom names, empty-name rejection, duplicate rollback, actual-name diagnostics, two concurrent instances with different permission modes, environments, and cleanup grace, cancellation isolation, and removal of one instance while its published run remains valid. The official product loopback tests run two named instances in one Host against separate model fixtures and prove independent unload and process-tree quiescence. Public Loader compositions mount two rows and two distinct tools for each product without starting either product, while keyless ACP snapshots pin the four-tool combined roster and the absence of a dynamic provider parameter.
Both product packages pin their default and custom names, empty-name rejection, duplicate rollback, actual-name diagnostics, two concurrent instances with different permission modes, environments, and cleanup grace, cancellation isolation, and removal of one instance while its published run remains valid. Claude Code additionally pins empty-model rejection, omitted-model inheritance, two explicit models, and exact per-query isolation through the real SDK/CLI fixture. Public Loader compositions mount two rows and two distinct tools for each product without starting either product, while keyless ACP snapshots pin the four-tool combined roster and the absence of dynamic provider or model parameters.
## Alternatives considered
@@ -43,6 +43,6 @@ Both product packages pin their default and custom names, empty-name rejection,
## Consequences
A Profile can expose several Codex and Claude Code tools backed by separate native permission modes and environments while existing configurations continue to resolve `codex` and `claude-code`. Provider and tool names remain independent configuration facts, so changing one requires updating the binding that refers to it.
A Profile can expose several Codex and Claude Code tools backed by separate native permission modes and environments, and Claude Code instances may additionally fix separate models, while existing configurations continue to resolve `codex` and `claude-code`. Provider and tool names remain independent configuration facts, so changing one requires updating the binding that refers to it.
The design adds no runtime renaming, model-visible selector, generated tool name, persistent instance directory, shared process pool, or compatibility alias. Correct multi-instance configurations require unique provider names and unique tool names; duplicate tool-name waiting remains a separate limitation.
The design adds no runtime renaming, model-visible provider or model selector, generated tool name, persistent instance directory, shared process pool, model discovery, fallback, or compatibility alias. Correct multi-instance configurations require unique provider names and unique tool names; duplicate tool-name waiting remains a separate limitation.
@@ -6,17 +6,17 @@ Status: implemented
## 问题
Profile 可以用多个配置项挂载同一个 Cordis 插件包,但 Codex 与 Claude Code 产品提供方此前会把每个配置项都注册到一个固定产品名称下。因此,第二个配置项会在其独立权限模式、环境或进程释放设置可用前因名称重复而失败。根据这些设置隐式派生名称会建立第二套身份规则,而在工具调用期间选择提供方会让模型输入决定部署权限。
Profile 可以用多个配置项挂载同一个 Cordis 插件包,但 Codex 与 Claude Code 产品提供方此前会把每个配置项都注册到一个固定产品名称下。因此,第二个配置项会在其支持时使用的独立模型、权限模式、环境或进程释放设置可用前因名称重复而失败。根据这些设置隐式派生名称会建立第二套身份规则,而在工具调用期间选择提供方会让模型输入决定部署权限。
现有 subagent 注册表已经拥有提供方名称唯一性、可逆注册、生命周期事件和由持有方拥有的已发布运行。现有 `dsh-tool-subagent` 配置也已经把一个提供方名称绑定到一个模型可见工具名称。产品提供方只需公开缺失的 Profile 所有身份,无需增加另一套注册表或选择协议。
## 决策
每个产品提供方 Config 都拥有非空的 `providerName`;默认值仍分别为 `codex``claude-code`。插件配置项加载时会固定解析后的名称,并把它作为 Provider 对象的 `name`;注册、查找、生命周期事件、运行日志和 HMR(热模块替换)移除因此使用同一个值。每个已挂载配置项保留自己的 `permissionMode``env``disposeGraceMs` 和运行资源。
每个产品提供方 Config 都拥有非空的 `providerName`;默认值仍分别为 `codex``claude-code`。插件配置项加载时会固定解析后的名称,并把它作为 Provider 对象的 `name`;注册、查找、生命周期事件、运行日志和 HMR(热模块替换)移除因此使用同一个值。每个已挂载配置项保留自己的 `permissionMode``env``disposeGraceMs` 和运行资源。Claude Code 配置项还会保留可选的非空 `model`;显式值为该实例的每次 query 固定,省略时仍以原生设置为权威。
当每个配置项使用不同的 `providerName` 时,Profile 可以挂载多个 Codex 或 Claude Code 配置项。每个 `dsh-tool-subagent` 配置项继续用已有的 `provider` 字段绑定这个准确名称,并公开独立配置的 `toolName`。工具调用不携带提供方选择器、别名或权限输入。重复提供方名称沿用现有 `DUPLICATE_PROVIDER` 路径失败,而且不会替换第一个注册项。
移除一个提供方配置项会阻止新的启动,并且只移除绑定到该名称的工具。该实例已经发布的运行仍由其持有方拥有,并会独立结算或 dispose(资源释放)。兄弟实例继续保持注册,并保留各自环境、原生权限模式、取消控制器、产品进程和清理宽限期。
移除一个提供方配置项会阻止新的启动,并且只移除绑定到该名称的工具。该实例已经发布的运行仍由其持有方拥有,并会独立结算或 dispose(资源释放)。兄弟实例继续保持注册,并保留各自支持时配置的模型、环境、原生权限模式、取消控制器、产品进程和清理宽限期。
### 所有权与生命周期
@@ -25,11 +25,11 @@ Profile 可以用多个配置项挂载同一个 Cordis 插件包,但 Codex 与
| 提供方实例名称 | 产品提供方 Config | 每个已挂载配置项拥有一个不可变注册名称;省略时使用现有默认值 |
| 名称唯一性与生命周期事件 | `ctx.subagents` | 重复注册失败;资源释放只移除匹配名称 |
| 模型可见工具名称与绑定 | `dsh-tool-subagent` Config | 一个静态工具解析一个已配置的提供方名称 |
| 权限、环境与进程清理 | 一个提供方实例 | 并发运行与兄弟实例不共享部署配置或运行资源 |
| 支持时的模型、权限、环境与进程清理 | 一个提供方实例 | 并发运行与兄弟实例不共享部署配置或运行资源 |
## 验证
两个产品包测试都会固定默认与自定义名称、空名称拒绝、重复注册回滚、实际名称诊断、使用不同权限模式、环境与清理宽限期的两个并发实例、取消隔离,以及移除一个实例后其已发布运行仍然有效。官方产品回环测试会在同一个 Host 中针对独立模型 fixture(测试前置数据)运行两个命名实例,并证明独立卸载与进程树完全停稳。公共 Loader 组合会为每个产品挂载两个配置项与两个不同工具,而且不启动任一产品;无密钥 ACP 快照固定最终四工具组合,并证明没有动态提供方参数。
两个产品包测试都会固定默认与自定义名称、空名称拒绝、重复注册回滚、实际名称诊断、使用不同权限模式、环境与清理宽限期的两个并发实例、取消隔离,以及移除一个实例后其已发布运行仍然有效。Claude Code 还会固定空模型拒绝、省略模型继承、两个显式模型,以及通过真实 SDK/CLI fixture 证明逐 query 精确隔离。公共 Loader 组合会为每个产品挂载两个配置项与两个不同工具,而且不启动任一产品;无密钥 ACP 快照固定最终四工具组合,并证明没有动态提供方或模型参数。
## 考虑过的替代方案
@@ -43,6 +43,6 @@ Profile 可以用多个配置项挂载同一个 Cordis 插件包,但 Codex 与
## 结果
Profile 可以公开多个由不同原生权限模式与环境支持的 Codex 与 Claude Code 工具,而现有配置仍会解析为 `codex``claude-code`。提供方名称与工具名称继续是彼此独立的配置事实,因此修改其中一项时必须同时更新引用它的绑定。
Profile 可以公开多个由不同原生权限模式与环境支持的 Codex 与 Claude Code 工具,Claude Code 实例还可分别固定模型,而现有配置仍会解析为 `codex``claude-code`。提供方名称与工具名称继续是彼此独立的配置事实,因此修改其中一项时必须同时更新引用它的绑定。
本设计不增加运行时改名、模型可见选择器、自动生成的工具名称、持久实例目录、共享进程池或兼容别名。正确的多实例配置要求提供方名称与工具名称都保持唯一;重复工具名称的等待问题仍是独立限制。
本设计不增加运行时改名、模型可见的提供方或模型选择器、自动生成的工具名称、持久实例目录、共享进程池、模型发现、fallback 或兼容别名。正确的多实例配置要求提供方名称与工具名称都保持唯一;重复工具名称的等待问题仍是独立限制。
@@ -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: faa14a5848a421389d8c427f844c4dedc118a0fd
2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md: 0b99cf8aebba4aa44fb42e635c94cc64b4d3b458
2026-08-12-production-dsh-excludes-product-subagent-providers.md: aaa6ddc70a0320c85d0d784441dfc82d74831a90
2026-08-12-production-dsh-excludes-product-subagent-providers.zh.md: 37b861ad879995ac7dbd91903cf92332a83f9d92
@@ -16,7 +16,7 @@ The two Bundles remain independent. The Codex Bundle owns the pinned official wr
## Verification
Package tests pin both Bundle manifests, published patches, exact self-provider rows, and product runtime dependencies. Claude coverage pins Agent SDK 0.3.220, Claude Code 2.1.220, all eight platform packages, SDK-selected execution, and missing-payload failure without host fallback. Codex coverage pins wrapper 0.147.0, all six platform aliases, package-declared execution, native descendant quiescence, and the same missing-payload behavior. Workspace validation derives each published patch from its Bundle declaration rather than a package catalog. Package/base assertions plus actual pnpm production evidence prove the default and selected-product dependency boundaries, while real Bundle-patch and Agent-Preset composition covers none, either product, both, the tool-grant intersection, later-Session adoption, and zero startup processes.
Package tests pin both Bundle manifests, published patches, exact self-provider rows, and product runtime dependencies. Claude coverage pins Agent SDK 0.3.241, Claude Code 2.1.241, all eight platform packages, SDK-selected execution, and missing-payload failure without host fallback. Codex coverage pins wrapper 0.147.0, all six platform aliases, package-declared execution, native descendant quiescence, and the same missing-payload behavior. Workspace validation derives each published patch from its Bundle declaration rather than a package catalog. Package/base assertions plus actual pnpm production evidence prove the default and selected-product dependency boundaries, while real Bundle-patch and Agent-Preset composition covers none, either product, both, the tool-grant intersection, later-Session adoption, and zero startup processes.
## Alternatives considered
@@ -16,7 +16,7 @@ Status: implemented
## 验证
包测试会固定两个 Bundle 的 manifest、发布 patch、准确的自身提供方行与产品运行时依赖。Claude 覆盖会固定 Agent SDK 0.3.220、Claude Code 2.1.220、八个平台包、SDK 所选执行路径,以及载荷缺失时不回退宿主命令的失败。Codex 覆盖会固定 wrapper 0.147.0、六个平台 alias、包声明的执行路径、原生后代进程停稳,以及同样的载荷缺失行为。工作区验证会从 Bundle 声明派生每份发布 patch,而非维护包目录。包与 base 断言加上实际 pnpm 生产证据会证明默认与所选产品的依赖边界;真实 Bundle patch 与 Agent Preset 组装则覆盖未安装、任一单包、双包、工具授权交集、后续 Session 采纳以及零启动进程。
包测试会固定两个 Bundle 的 manifest、发布 patch、准确的自身提供方行与产品运行时依赖。Claude 覆盖会固定 Agent SDK 0.3.241、Claude Code 2.1.241、八个平台包、SDK 所选执行路径,以及载荷缺失时不回退宿主命令的失败。Codex 覆盖会固定 wrapper 0.147.0、六个平台 alias、包声明的执行路径、原生后代进程停稳,以及同样的载荷缺失行为。工作区验证会从 Bundle 声明派生每份发布 patch,而非维护包目录。包与 base 断言加上实际 pnpm 生产证据会证明默认与所选产品的依赖边界;真实 Bundle patch 与 Agent Preset 组装则覆盖未安装、任一单包、双包、工具授权交集、后续 Session 采纳以及零启动进程。
## 考虑过的替代方案
@@ -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-21-product-subagent-minimal-diagnostics.md
2026-08-21-product-subagent-minimal-diagnostics.md: a4d51c325738e6e3e6c74561deb877c9bca3c016
2026-08-21-product-subagent-minimal-diagnostics.zh.md: 409b548e0e6169479cec892d349dc8c969b24637
@@ -0,0 +1,59 @@
# Agent Note: Product subagents expose minimal actionable diagnostics
Status: implemented
English | [中文](2026-08-21-product-subagent-minimal-diagnostics.zh.md)
## Problem
The Claude Code and Codex providers receive structured failures from independently versioned product runtimes. Mirroring every member of those upstream error unions into model-visible diagnostics makes each runtime upgrade expand the Provider contract even when the parent agent would take the same next action for several categories.
Raw SDK errors, app-server payloads, stderr, commands, paths, task content, environment values, and credentials cannot replace the structured mapping because they cross the product process boundary without a stable safety guarantee. The parent still needs enough information to distinguish a product limit, access restriction, service or transport problem, invalid result, and managed process failure.
## Decision
Each product Provider derives a small action category from the current operation and safe structured product facts. The existing `SubagentResult.diagnostic` string remains the only public representation: consumers display it but do not parse it, and the shared subagent result boundary continues to enforce the complete 4096-byte UTF-8 limit.
Claude Code maps Agent SDK results into five categories:
| Category | Safe input |
| --- | --- |
| `limit` | Turn, budget, or structured-output retry limits |
| `product-error` | A general SDK execution failure |
| `invalid-result` | An error-marked or blank success, or no terminal result |
| `process` | The managed CLI exits before a terminal result |
| `unknown` | Startup, teardown, unrecognized SDK values, or failures without a more specific safe fact |
The diagnostic also retains the derived `query-start`, `query-run`, `process`, or `teardown` stage and independently observed exit code and signal. A contributing permission decision follows the failure line. Successful completion and local cancellation expose no failure diagnostic, and original SDK text remains only on the internal cause chain and in Host observation.
Codex currently retains the version-pinned categories documented by the [structured failure-facts decision](../feature/2026-08-18-product-subagent-failure-facts.md). The two providers may adopt the minimal mapping independently because neither shares a public error enum or asks a consumer to branch on category text.
### Ownership and lifecycle
| Fact or operation | Owner | Result |
| --- | --- | --- |
| Product error interpretation | Official product runtime | The Provider consumes only structured facts exposed by its pinned integration |
| Action category and stage | One product Provider run | Derived at the failure site and discarded after result settlement |
| Exit code and signal | `dsh-subprocess` handle | Displayed independently when observed, without inferring missing values |
| Permission decision | Product Provider permission callbacks or protocol | Appended only when it contributed to the failed run |
| Diagnostic delivery and byte limit | `dsh-subagent` and its foreground or Job consumers | One bounded optional string remains separate from assistant output |
## Verification
Claude Code package tests cover every coarse category, all four stages, unknown structured values, permission ordering, raw-text exclusion, success and cancellation omission, concurrent-run isolation, and independent exit code and signal fields. The real Agent SDK 0.3.241 and Claude Code 2.1.241 fixture produces an actual max-turns limit, process failure, permission denial, strict final answer, cancellation, and whole-tree quiescence. Loader and keyless product compositions continue to expose static tools without a diagnostic parser or model-visible category input.
## Alternatives considered
**Preserve every upstream enum member.** Several members lead to the same parent action, while the display-only consumer gains no behavior from their exact names. Keeping them would make routine product upgrades redefine a larger Harness-facing promise.
**Return raw product text or classify general stderr.** Free-form text can contain sensitive task and environment data and has no stable versioned meaning. Only structured product facts, Provider stage, permission decisions, and managed process outcomes qualify as input.
**Add a shared structured error type.** The products version their failures independently, and current consumers only present a bounded string. A shared enum would move product release churn into the provider-neutral seam without a consumer that needs it.
**Retry or fall back based on the category.** The Provider owns one one-shot run and has no retry authority, fallback model, persistent product session, or recovery state. The category informs the parent; it does not start another product action.
## Consequences
Claude Code upgrades no longer require a model-visible promise for every SDK error subtype. Parents still distinguish a limit, general product failure, invalid result, managed process exit, and unknown failure while retaining stage, permission, and process facts.
The diagnostic remains safe display text rather than a recovery protocol. This change adds no raw error forwarding, fallback model, automatic retry, product session persistence, public structured result field, or dynamic provider and model selection.
@@ -0,0 +1,59 @@
# Agent Note: 产品 subagent 公开最小可行动诊断
Status: implemented
[English](2026-08-21-product-subagent-minimal-diagnostics.md) | 中文
## Problem
Claude Code 与 Codex 提供方会从彼此独立版本化的产品运行时接收结构化失败。如果把这些上游错误联合的每个成员都镜像进模型可见诊断,那么即使多个类别会让父 agent 采取同一种下一步动作,每次运行时升级仍会扩大提供方约定。
原始 SDK 错误、app-server payload、stderr、命令、路径、任务内容、环境值与凭证都不能取代结构化映射,因为这些内容跨越产品进程边界时没有稳定的安全保证。父 agent 仍需要足够信息来区分产品限制、访问限制、服务或传输问题、无效结果与受管进程失败。
## Decision
每个产品提供方都会根据当前操作与安全的结构化产品事实派生少量行动类别。现有 `SubagentResult.diagnostic` 字符串仍是唯一公共表示:消费方只展示而不解析,共享 subagent 结果边界继续对完整文本执行 4096 字节 UTF-8 上限。
Claude Code 会把 Agent SDK 结果映射为五种类别:
| 类别 | 安全输入 |
| --- | --- |
| `limit` | 轮次、预算或结构化输出重试限制 |
| `product-error` | 一般 SDK 执行失败 |
| `invalid-result` | 标记为错误或内容空白的成功结果,或缺失终态结果 |
| `process` | 受管 CLI 在终态结果前退出 |
| `unknown` | 启动、清理、无法识别的 SDK 值,或没有更具体安全事实的失败 |
诊断还会保留派生的 `query-start``query-run``process``teardown` 阶段,以及分别观测到的退出码与信号。参与失败的权限决定会跟在失败行之后。成功完成与本地取消不公开失败诊断,原始 SDK 文本只留在内部 cause 链和 Host 观测中。
Codex 当前仍保留[结构化失败事实决策](../feature/2026-08-18-product-subagent-failure-facts.zh.md)记录的锁定版本类别。两个提供方可以分别采用最小映射,因为它们既不共享公共错误枚举,也不要求消费方根据类别文本分支。
### 所有权与生命周期
| 事实或操作 | 责任方 | 结果 |
| --- | --- | --- |
| 产品错误解释 | 官方产品运行时 | 提供方只消费其锁定集成公开的结构化事实 |
| 行动类别与阶段 | 单次产品提供方运行 | 在失败位置派生,并在结果结算后丢弃 |
| 退出码与信号 | `dsh-subprocess` 句柄 | 观测到时分别展示,不推断缺失值 |
| 权限决定 | 产品提供方权限回调或协议 | 只在参与失败运行时追加 |
| 诊断传递与字节上限 | `dsh-subagent` 及其前台或 Job 消费方 | 一个有界可选字符串继续与 assistant 输出分离 |
## Verification
Claude Code 包测试覆盖全部粗粒度类别、四个阶段、未知结构化值、权限顺序、原始文本排除、成功与取消时省略、并发运行隔离,以及彼此独立的退出码与信号字段。真实 Agent SDK 0.3.241 与 Claude Code 2.1.241 fixture 会产生实际 max-turns 限制、进程失败、权限拒绝、严格最终答案、取消与整棵进程树完全停稳。Loader 与无密钥产品组合继续公开静态工具,不增加诊断解析器或模型可见类别输入。
## Alternatives considered
**保留每个上游枚举成员。** 多个成员会触发同一种父级动作,而只负责展示的消费方无法从准确名称获得额外行为。继续保留会让常规产品升级重新定义更大的 Harness 可见承诺。
**返回原始产品文本或分类一般 stderr。** 自由文本可能包含敏感任务与环境数据,也没有稳定的版本化含义。只有结构化产品事实、提供方阶段、权限决定与受管进程结果可以成为输入。
**增加共享结构化错误类型。** 两个产品独立版本化失败,而当前消费方只呈现有界字符串。共享枚举会在没有消费需求的情况下,把产品发布变动移入提供方无关的 seam。
**根据类别重试或 fallback。** 提供方只拥有一次 one-shot 运行,没有重试权限、fallback 模型、持久产品会话或恢复状态。类别只用于通知父级,不会启动另一项产品动作。
## Consequences
Claude Code 升级不再要求为每个 SDK 错误 subtype 提供模型可见承诺。父级仍可区分限制、一般产品失败、无效结果、受管进程退出与未知失败,并保留阶段、权限与进程事实。
诊断仍是安全展示文本,而不是恢复协议。本改动不增加原始错误转发、fallback 模型、自动重试、产品会话持久化、公共结构化结果字段或动态提供方与模型选择。
+9 -9
View File
@@ -113,18 +113,18 @@ pnpm applies local patches to the following packages at install time, so shipped
The project owner authorizes distribution of every version of the official `@anthropic-ai/claude-agent-sdk` package and the official Claude Code CLI/platform payloads that each version declares through `optionalDependencies`. This identity-scoped authorization does not classify their declared terms as permissive and does not cover any unrelated runtime package; version, declared-license, and payload-set changes still require the ordinary dependency, lockfile, compatibility, terms, and notices review.
The installed SDK 0.3.220 declares the following optional platform packages. Each carries the official Claude Code 2.1.220 executable; the package identities and versions come from the SDK manifest, while the declared license field is verified against the platform payload installed for the current host.
The installed SDK 0.3.241 declares the following optional platform packages. Each carries the official Claude Code 2.1.241 executable; the package identities and versions come from the SDK manifest, while the declared license field is verified against the platform payload installed for the current host.
| Optional platform package | Version | Declared license |
| --- | --- | --- |
| [`@anthropic-ai/claude-agent-sdk-darwin-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-darwin-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-arm64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64-musl) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-x64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64-musl) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-win32-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-arm64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-win32-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-x64) | 0.3.220 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-darwin-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-darwin-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-darwin-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-arm64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-arm64-musl) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-linux-x64-musl`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-linux-x64-musl) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-win32-arm64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-arm64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
| [`@anthropic-ai/claude-agent-sdk-win32-x64`](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk-win32-x64) | 0.3.241 | SEE LICENSE IN LICENSE.md |
## Development-only npm dependencies
+2 -2
View File
@@ -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 docs/config-catalog.md
config-catalog.md: a411d8aa6868157ab54bef04a4344b6eb69e2e62
config-catalog.zh.md: 4fe92b504e5c803012dacce342f02e8191c300b6
config-catalog.md: a6ecf3f32eba84f6686a16323e61ccaf6c0e2cce
config-catalog.zh.md: fbbdf1f4bf09f8d3003dc728c52529b58ab9d747
+3 -1
View File
@@ -2273,10 +2273,12 @@ Source: [`packages/subagent/subagent-acp/src/index.ts:27`](../packages/subagent/
Requires: `subagents` · `subprocess`
```ts config-catalog
/** Deployment-owned permission, environment, and process-release settings. */
/** Deployment-owned model, permission, environment, and process-release settings. */
export interface Config {
/** Provider name on `ctx.subagents` (default `claude-code`). */
providerName?: string
/** Native Claude model fixed for this instance; omitted to inherit Claude settings. */
model?: string
/**
* Explicit environment entries layered over the subprocess seam's
* credential-scrubbed parent environment.
+3 -1
View File
@@ -2275,10 +2275,12 @@ export type PermissionPolicy = 'allow' | 'reject'
需要:`subagents` · `subprocess`
```ts config-catalog
/** Deployment-owned permission, environment, and process-release settings. */
/** Deployment-owned model, permission, environment, and process-release settings. */
export interface Config {
/** Provider name on `ctx.subagents` (default `claude-code`). */
providerName?: string
/** Native Claude model fixed for this instance; omitted to inherit Claude settings. */
model?: string
/**
* Explicit environment entries layered over the subprocess seam's
* credential-scrubbed parent environment.
@@ -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: 30b61347dfda110fe95a9153aeea35c8f760012f
README.zh.md: 17ddac7d4beebfbf0f374f606a68d77b13ec2b77
README.md: 4171c098f910f4c34ae396a965d14fd947f4dec3
README.zh.md: 30838dc90b08e4154f282dd373178fa009403d70
@@ -8,13 +8,13 @@ This package registers a Profile-named Claude Code subagent provider whose defau
`start(request)` accepts only a non-empty sequence of text blocks and derives the child cwd from the parent Session. It creates one private `AbortController`, calls the official SDK `query()`, and publishes the run only after the SDK's `spawnClaudeCodeProcess` hook has supplied a live CLI handle owned by [`dsh-subprocess`](../../subprocess/subprocess/README.md). A failure or cancellation before publication closes the query, terminates any acquired process tree, waits for it to exit, and rejects `start()`.
The SDK receives the exact concatenated text task. The provider iterates the complete SDK message stream and accepts only a `result` message with `subtype: "success"`, `is_error: false`, and a nonblank `result`, followed by normal iterator completion. Every failure still maps to `error`: the four error subtypes in Agent SDK 0.3.220 retain their exact category, an error-marked or blank success becomes `invalid-success`, a missing result becomes `missing-result`, an unclassified query failure becomes `unknown`, and an early CLI exit becomes `process-exit`. The diagnostic also names the current `query-start`, `query-run`, `process`, or `teardown` stage and independently includes an observed exit code and signal. The provider produces neither `max-tokens` nor `refusal`.
The SDK receives the exact concatenated text task. The provider iterates the complete SDK message stream and accepts only a `result` message with `subtype: "success"`, `is_error: false`, and a nonblank `result`, followed by normal iterator completion. Every failure still maps to `error`: turn, budget, and structured-output limits use `limit`; execution failures use `product-error`; error-marked, blank, or missing results use `invalid-result`; an early CLI exit uses `process`; and unclassified failures use `unknown`. The diagnostic also names the current `query-start`, `query-run`, `process`, or `teardown` stage and independently includes an observed exit code and signal. The provider produces neither `max-tokens` nor `refusal`.
Local cancellation wins the result race and maps to `aborted` without a failure diagnostic. `dispose()` is idempotent: it aborts the run, asks the SDK query to close, invokes the shared process-tree termination escalation, and waits for whole-tree exit. SDK graceful close expresses protocol intent; the subprocess handle remains the authority for process quiescence. Startup and teardown rejections expose the same fixed safe stage and process facts through their Error message, while the original product or Host error remains on the internal cause chain and in the Provider's Host log. Result failure and independent teardown failure remain separate.
## Native settings and interaction
The provider deliberately omits the SDK `settingSources` option. The official SDK therefore reads the host's normal user, project, and local Claude settings relative to the parent Session cwd, including native account state and product configuration. The provider neither copies nor filters those files and does not create or modify login state. The Profile-selected `permissionMode` is the one query-level override: Claude Code still owns its settings and sandbox, while the selected native mode decides how this unattended query handles permission checks.
The provider deliberately omits the SDK `settingSources` option. The official SDK therefore reads the host's normal user, project, and local Claude settings relative to the parent Session cwd, including native account state and product configuration. The provider neither copies nor filters those files and does not create or modify login state. A configured `model` is passed unchanged to that Provider instance's queries; when it is omitted, the Provider leaves `Options.model` absent and native settings remain authoritative. The Profile-selected `permissionMode` always decides how the unattended query handles permission checks, while Claude Code still owns model interpretation, settings, and sandbox behavior.
Each query sets `persistSession: false` and disables `AskUserQuestion`. Except in bypass mode, `canUseTool` immediately denies requests that still require human approval. Plan mode also places `ExitPlanMode` in the SDK's `disallowedTools`, so native settings cannot pre-approve a transition back to execution and the model must return the completed plan as its final answer. MCP elicitation is declined, the known refusal fallback dialog is cancelled, and undeclared dialog kinds use the SDK's no-dialog failure behavior. These decisions never wait for a user interface. When both facts contribute to a failed run, `SubagentResult.diagnostic` contains the structured failure line first and the latest safe permission decision second; the shared result boundary limits the complete text to 4096 UTF-8 bytes. Successful and locally cancelled runs expose neither captured fact.
@@ -27,6 +27,7 @@ The provider advertises no optional start-time capabilities and reports `inherit
| Key | Default | Meaning |
|---|---|---|
| `providerName` | `claude-code` | Non-empty registry name on `ctx.subagents`; each mounted instance needs a unique value. |
| `model` | native Claude settings | Optional non-empty native model name fixed for every run from this Provider instance; omission sends no SDK override. |
| `env` | `{}` | Explicit SDK/CLI environment layered over the shared credential-scrubbed parent environment. |
| `permissionMode` | `dontAsk` | Native non-interactive permission policy fixed for every run from this Provider instance. |
| `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. |
@@ -39,7 +40,7 @@ The provider advertises no optional start-time capabilities and reports `inherit
| `plan` | Run in native planning mode, deny execution approval, and return the completed plan as the final answer. |
| `bypassPermissions` | Explicitly set the SDK's dangerous confirmation and bypass permission checks. |
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, while `permissionMode` is the only query-level policy override. 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.
Production omits `pathToClaudeCodeExecutable`, so Agent SDK 0.3.241 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`. A configured `model` is a direct SDK override owned by the Provider instance; the provider does not discover model names, rewrite aliases, or set a fallback, and omission preserves native model selection. Native settings and authentication remain authoritative for every other product choice. The plugin does not 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; 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.
@@ -49,7 +50,7 @@ dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-claude-code
dsh --profile <name>
```
Installation controls Host availability, not model permission. The Bundle supplies the dormant default `claude-code` row; the Profile may replace that row's complete config or mount additional rows with distinct `providerName`, `permissionMode`, and `env` values. Loading an instance starts no Claude process until a bound tool calls it. Each `dsh-tool-subagent` row names one provider and needs its own `toolName`, so the model sees static tools rather than a dynamic provider selector. Full Agent Presets carry a matching default product tool row with `disabled: true`; copy a preset and remove that field to expose `subagent_claude_code` only to agents composed from the copy. Its `one-shot` policy keeps omitted or `false` `run_in_background` calls in the foreground, while explicit `true` returns a parent-owned Job id for `job_output` or `job_kill`. The base host and full presets already provide the generic Job registry and controls.
Installation controls Host availability, not model permission. The Bundle supplies the dormant default `claude-code` row; the Profile may replace that row's complete config or mount additional rows with distinct `providerName`, `model`, `permissionMode`, and `env` values. Loading an instance starts no Claude process until a bound tool calls it. Each `dsh-tool-subagent` row names one provider and needs its own `toolName`, so the model sees static tools rather than a dynamic provider or model selector. Full Agent Presets carry a matching default product tool row with `disabled: true`; copy a preset and remove that field to expose `subagent_claude_code` only to agents composed from the copy. Its `one-shot` policy keeps omitted or `false` `run_in_background` calls in the foreground, while explicit `true` returns a parent-owned Job id for `job_output` or `job_kill`. The base host and full presets already provide the generic Job registry and controls.
The standalone composition below shows the complete explicit capability. A Profile based on `@deepseek-ai/dsh-base` keeps its existing Job rows, adds the product provider and tool rows, and does not mount duplicate Job services.
@@ -58,6 +59,7 @@ The standalone composition below shows the complete explicit capability. A Profi
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-safe
model: approved-review-model
permissionMode: dontAsk
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
@@ -66,6 +68,7 @@ The standalone composition below shows the complete explicit capability. A Profi
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-bypass
model: approved-edit-model
permissionMode: bypassPermissions
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
@@ -98,7 +101,7 @@ The standalone composition below shows the complete explicit capability. A Profi
## Product compatibility and evidence
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 installing the Bundle registers only the dormant Claude Code provider and starts no product process.
The runtime dependency is pinned to `@anthropic-ai/claude-agent-sdk@0.3.241`, whose eight platform packages carry Claude Code 2.1.241. 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 92,295,035 packed bytes and 325,056,216 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. It also proves that an omitted model comes from native settings and two named instances send their distinct configured models. Loader composition proves that installing the Bundle registers only dormant Claude Code providers and starts no product process.
Installing with optional dependencies omitted, using an unsupported platform, or losing the selected payload leaves provider registration dormant but makes the first delegation fail at the SDK startup boundary. The caller receives the safe `query-start` / `unknown` failure fact; the native payload error remains only on the internal cause chain and in the Provider's Host log. The provider neither probes a host CLI nor retries with one.
@@ -112,7 +115,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; its model, system instructions, tools, sandbox, and authentication come from native Claude settings, the selected Provider instance's Profile configuration fixes the query's environment and non-interactive permission mode, and the executable version comes from the Bundle's pinned SDK platform payload.
The Claude Code child receives the standalone text task as one fresh SDK query. Its workspace is the parent Session cwd; the selected Provider instance fixes the query's configured model, environment, and non-interactive permission mode, while an omitted model and every other product setting come from native Claude configuration. The executable version comes from the Bundle's pinned SDK platform payload.
#### Token effect
@@ -126,7 +129,7 @@ Independent of the parent request cache. Reuse depends only on Claude Code's own
#### What the model sees
Through `dsh-tool-subagent`, a foreground call gives the parent the strict final Claude Code answer or an error containing the stop reason and optional safe diagnostic for a non-completed result. That diagnostic can distinguish the fixed SDK error category, lifecycle stage, and observed process outcome without copying raw product text. A background call first returns a Job id; the generic job controls later deliver a completion notice, expose the same final answer or failed status detail through `job_output`, and let `job_kill` request cancellation. Claude Code reasoning, tool activity, intermediate messages, stderr, workspace diffs, usage, product ids, tool inputs, and raw protocol payloads are not copied into the parent Session.
Through `dsh-tool-subagent`, a foreground call gives the parent the strict final Claude Code answer or an error containing the stop reason and optional safe diagnostic for a non-completed result. That diagnostic can distinguish a coarse action category, lifecycle stage, and observed process outcome without copying raw product text or version-specific subtype names. A background call first returns a Job id; the generic job controls later deliver a completion notice, expose the same final answer or failed status detail through `job_output`, and let `job_kill` request cancellation. Claude Code reasoning, tool activity, intermediate messages, stderr, workspace diffs, usage, product ids, tool inputs, and raw protocol payloads are not copied into the parent Session.
#### Token effect
@@ -139,8 +142,8 @@ Append-only: foreground adds one result after the reusable parent prefix, while
## Known Limitations and Deferred Work
- **One fresh query and process per run** — there is no continuation, resume, pooling, progress stream, or product-session persistence.
- **Static instance selection** — Profile rows fix provider names and tool bindings; calls cannot choose a provider dynamically, and every exposed tool needs a unique `toolName`.
- **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.
- **Static instance selection** — Profile rows fix provider names, optional models, and tool bindings; calls cannot choose or change either a provider or model dynamically, and every exposed tool needs a unique `toolName`.
- **Host settings are intentionally authoritative**when `model` is omitted, project and user settings choose it; native settings always retain the remaining tools and behavior, and the provider does not provide a filtered or hermetic production mode.
- **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 with their lifecycle stage and the safe `unknown` fallback rather than a separate public classification.
- **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, permission prompts are denied, MCP elicitation is declined, and blocking dialogs fail closed instead of suspending.
@@ -8,13 +8,13 @@
`start(request)` 只接受非空的文本块序列,并根据父会话确定子级 cwd。它会创建一个私有 `AbortController`,调用官方 SDK 的 `query()`,并仅在 SDK 的 `spawnClaudeCodeProcess` 钩子已经提供由 [`dsh-subprocess`](../../subprocess/subprocess/README.zh.md) 管理的活动 CLI 句柄后发布此次运行。若在发布前发生失败或取消,它会关闭 query、终止所有已取得的进程树并等待其退出,然后拒绝 `start()` 调用。
SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK 消息流,而且只接受满足以下条件的 `result` 消息:其 `subtype: "success"``is_error: false``result` 非空白,之后迭代器还须正常结束。所有失败仍映射为 `error`Agent SDK 0.3.220 的四种错误子类型保留准确类别;标记为错误内容空白的成功消息成为 `invalid-success`;缺失结果成为 `missing-result`;未分类的 query 失败成为 `unknown`CLI 提前退出成为 `process-exit`。诊断还会注明当前 `query-start``query-run``process``teardown` 阶段,并分别保留已观测到的退出码与信号。该提供方不会产生 `max-tokens``refusal`
SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK 消息流,而且只接受满足以下条件的 `result` 消息:其 `subtype: "success"``is_error: false``result` 非空白,之后迭代器还须正常结束。所有失败仍映射为 `error`轮次、预算与结构化输出限制使用 `limit`;执行失败使用 `product-error`;标记为错误内容空白或缺失的结果使用 `invalid-result`CLI 提前退出使用 `process`;未分类失败使用 `unknown`。诊断还会注明当前 `query-start``query-run``process``teardown` 阶段,并分别保留已观测到的退出码与信号。该提供方不会产生 `max-tokens``refusal`
本地取消会在结果竞态中胜出并映射为 `aborted`,且不附带失败诊断。`dispose()`(资源释放)具有幂等性:它会中止此次运行、请求 SDK query 关闭、调用共享的进程树逐级终止机制,并等待整棵进程树退出。SDK 的优雅关闭只表达协议意图;进程是否完全停稳仍以子进程句柄为准。启动与清理拒绝会在 Error 消息中公开同样固定的安全阶段和进程事实,而原始产品或 Host 错误只保留在内部 cause 链与提供方的 Host 日志中。结果失败与独立的清理失败仍彼此分离。
## 原生设置与交互
提供方故意省略 SDK 的 `settingSources` 选项。因此,官方 SDK 会相对于父会话 cwd 读取宿主机常规的用户、项目和本地 Claude 设置,包括原生账户状态与产品配置。提供方既不复制也不过滤这些文件,也不会创建或修改登录状态。Profile 选择的 `permissionMode` 是唯一的 query 级覆盖:Claude Code 仍拥有其设置与沙箱,而所选原生模式决定这个无人值守 query 如何处理权限检查
提供方故意省略 SDK 的 `settingSources` 选项。因此,官方 SDK 会相对于父会话 cwd 读取宿主机常规的用户、项目和本地 Claude 设置,包括原生账户状态与产品配置。提供方既不复制也不过滤这些文件,也不会创建或修改登录状态。若配置了 `model`,提供方会把它原样传给该实例的每次 query;若省略,提供方不会设置 `Options.model`,原生设置继续拥有模型选择权。Profile 选择的 `permissionMode` 始终决定无人值守 query 如何处理权限检查,而模型解释、其他设置与沙箱行为仍由 Claude Code 负责
每次 query 都设置 `persistSession: false` 并禁用 `AskUserQuestion`。除 bypass 模式外,`canUseTool` 会立即拒绝仍需人工审批的请求。Plan 模式还会把 `ExitPlanMode` 放入 SDK 的 `disallowedTools`,因此原生 settings 无法预先放行回到执行模式的转换,模型必须把完整计划作为最终答案返回。MCP elicitation 会被拒绝,已知的拒绝回退对话会被取消,未声明的对话类型则使用 SDK 的无对话失败行为。这些决定都不会等待用户界面。当两类事实共同参与一次失败运行时,`SubagentResult.diagnostic` 会先写入结构化失败行,再写入最新的安全权限决定;共享结果边界会把完整文本限制在 4096 个 UTF-8 字节以内。成功运行与本地取消都不会公开已捕获的事实。
@@ -27,6 +27,7 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
| 配置键 | 默认值 | 含义 |
|---|---|---|
| `providerName` | `claude-code` | `ctx.subagents` 中的非空注册名称;每个已挂载实例都需要唯一值。 |
| `model` | Claude 原生设置 | 可选的非空原生模型名称,为该实例的每次运行固定;省略时不发送 SDK 覆盖。 |
| `env` | `{}` | 显式指定的 SDK/CLI 环境,叠加在由共享机制清除凭证后的父环境之上。 |
| `permissionMode` | `dontAsk` | 为该提供方实例的每次运行固定原生非交互权限策略。 |
| `disposeGraceMs` | `3000` | 共享进程树责任方各终止层级之间的宽限期,单位为毫秒且须为正有限值,并不得大于仓库共享的 [`MAX_TIMER_DELAY_MS`](../../util/timeout/README.zh.md);随后资源释放会等待整棵进程树退出。 |
@@ -39,7 +40,7 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
| `plan` | 使用原生规划模式,拒绝执行审批,并把完整计划作为最终答案返回。 |
| `bypassPermissions` | 显式设置 SDK 的危险确认并跳过权限检查。 |
生产环境会省略 `pathToClaudeCodeExecutable`,因此 Agent SDK 0.3.220 会从自己的平台包中选择匹配的原生 `claude``claude.exe`,再通过 custom-spawn 钩子把该绝对命令交给 `dsh-subprocess`。提供方不会检查 `PATH`、重复实现平台选择,也不会回退到宿主 `claude`原生设置与身份验证继续是权威来源,而 `permissionMode`唯一的 query 级策略覆盖。本插件不选择模型、不创建产品主目录、执行登录,也不探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或 token 必须在该配置中显式提供。除非被覆盖,`ANTHROPIC_BASE_URL` 等非凭证端点变量以及 `PATH``HOME` 等普通环境变量仍会被继承;`PATH` 不参与选择 Claude 可执行文件。
生产环境会省略 `pathToClaudeCodeExecutable`,因此 Agent SDK 0.3.241 会从自己的平台包中选择匹配的原生 `claude``claude.exe`,再通过 custom-spawn 钩子把该绝对命令交给 `dsh-subprocess`。提供方不会检查 `PATH`、重复实现平台选择,也不会回退到宿主 `claude`已配置的 `model`由提供方实例拥有的直接 SDK 覆盖;提供方不会发现模型名称、改写别名或设置 fallback,省略该字段会保留原生模型选择。其余产品选择仍以原生设置与身份验证为权威来源。本插件不会创建产品主目录、执行登录探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或 token 必须在该配置中显式提供。除非被覆盖,`ANTHROPIC_BASE_URL` 等非凭证端点变量以及 `PATH``HOME` 等普通环境变量仍会被继承;`PATH` 不参与选择 Claude 可执行文件。
本包是可选的 Profile Bundle。将它安装进目标 Profile 后重启该 Profile;安装会把锁定的 Agent SDK 与一个兼容的平台 CLI 载荷带入该 Profile,而包所声明的 `cordis.patch.yml` 层只注册休眠的 `claude-code` Host provider,不会启动 Claude 进程。移除该包后,下一次 Profile 启动会撤回这一 provider 及其私有运行时闭包。
@@ -49,7 +50,7 @@ dsh plugin --profile <name> remove @deepseek-ai/dsh-subagent-claude-code
dsh --profile <name>
```
安装决定 Host 可用性,而不是模型权限。Bundle 会提供休眠的默认 `claude-code` 配置项;Profile 可以替换该配置项的完整 config,也可以挂载更多具有不同 `providerName``permissionMode``env` 的配置项。加载实例本身不会在绑定工具调用前启动 Claude 进程。每个 `dsh-tool-subagent` 配置项指定一个提供方,并需要独立的 `toolName`,因此模型看到的是静态工具,而不是动态提供方选择器。完整 Agent Preset 携带对应的默认产品工具行并设置 `disabled: true`;复制一个 preset 后删除该字段,即可只向由该副本组装的 agent 暴露 `subagent_claude_code`。其 `one-shot` 策略会让省略 `run_in_background` 或传入 `false` 的调用继续在前台等待,而显式传入 `true` 会返回由父 agent 拥有的 Job ID,供 `job_output``job_kill` 使用。base host(基础宿主)与完整 preset 已提供通用作业注册表和控制工具。
安装决定 Host 可用性,而不是模型权限。Bundle 会提供休眠的默认 `claude-code` 配置项;Profile 可以替换该配置项的完整 config,也可以挂载更多具有不同 `providerName``model``permissionMode``env` 的配置项。加载实例本身不会在绑定工具调用前启动 Claude 进程。每个 `dsh-tool-subagent` 配置项指定一个提供方,并需要独立的 `toolName`,因此模型看到的是静态工具,而不是动态提供方或模型选择器。完整 Agent Preset 携带对应的默认产品工具行并设置 `disabled: true`;复制一个 preset 后删除该字段,即可只向由该副本组装的 agent 暴露 `subagent_claude_code`。其 `one-shot` 策略会让省略 `run_in_background` 或传入 `false` 的调用继续在前台等待,而显式传入 `true` 会返回由父 agent 拥有的 Job ID,供 `job_output``job_kill` 使用。base host(基础宿主)与完整 preset 已提供通用作业注册表和控制工具。
下列独立组装展示完整的显式能力。基于 `@deepseek-ai/dsh-base` 的 Profile 保留已有 Job 配置项,新增产品提供方与工具配置项,而且不重复挂载 Job 服务。
@@ -58,6 +59,7 @@ dsh --profile <name>
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-safe
model: approved-review-model
permissionMode: dontAsk
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
@@ -66,6 +68,7 @@ dsh --profile <name>
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-bypass
model: approved-edit-model
permissionMode: bypassPermissions
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
@@ -98,7 +101,7 @@ dsh --profile <name>
## 产品兼容性与证据
运行时依赖精确锁定为 `@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 组合证明安装该 Bundle 只会注册休眠的 Claude Code provider,不会启动产品进程。
运行时依赖精确锁定为 `@anthropic-ai/claude-agent-sdk@0.3.241`,其八个平台包都携带 Claude Code 2.1.241。普通安装会按当前操作系统、CPU 及 Linux libc 选择一个载荷。对于当前 darwin-arm64 载荷,`npm pack --dry-run --json` 报告压缩包为 92,295,035 字节、解包后为 325,056,216 字节;其他平台可能不同,这些数值只用于披露而不是安装阈值。无密钥真实产品测试会让 SDK 选择 CLI,通过回环 Messages fixture 运行它,并断言共享子进程 argv 的首项就是该平台包的原生可执行文件;它还证明省略 model 时使用原生设置,两个命名实例则发送各自配置的模型。Loader 组合证明安装该 Bundle 只会注册休眠的 Claude Code provider,不会启动产品进程。
如果安装时省略 optional dependencies、当前平台不受支持,或所选载荷缺失,提供方注册仍保持休眠,但第一次委派会在 SDK 启动边界失败。调用方只会收到安全的 `query-start` / `unknown` 失败事实;原生载荷错误只保留在内部 cause 链和提供方 Host 日志中。提供方既不会探测宿主 CLI,也不会用它重试。
@@ -112,7 +115,7 @@ Loader 组合证明 Bundle 默认实例、两个额外命名 Claude 实例与现
#### 模型看到的内容
Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。它的工作区是父会话 cwd;其模型、系统指令、工具、沙箱和身份验证来自原生 Claude 设置,所选提供方实例的 Profile 配置会固定该 query 的环境与非交互权限模式,而可执行版本来自 Bundle 锁定的 SDK 平台载荷。
Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。它的工作区是父会话 cwd;所选提供方实例会固定已配置的模型、环境与非交互权限模式,而省略的模型及其余产品设置来自 Claude 原生配置。可执行版本来自 Bundle 锁定的 SDK 平台载荷。
#### 对 token 的影响
@@ -126,7 +129,7 @@ Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。
#### 模型看到的内容
通过 `dsh-tool-subagent`,前台调用会让父级模型看到符合严格成功条件的 Claude Code 最终答案;若结果未完成,错误中会包含终止原因和可选的安全诊断。该诊断可以区分固定 SDK 错误类别、生命周期阶段和已观测的进程结果,而不复制原始产品文本。后台调用会先返回 Job id;随后通用作业控制面会送达完成通知,通过 `job_output` 公开同一最终答案或失败状态 detail,并允许 `job_kill` 请求取消。Claude Code 的推理、工具活动、中间消息、stderr、工作区差异、用量信息、产品标识符、工具输入和原始协议载荷均不会复制到父会话。
通过 `dsh-tool-subagent`,前台调用会让父级模型看到符合严格成功条件的 Claude Code 最终答案;若结果未完成,错误中会包含终止原因和可选的安全诊断。该诊断可以区分粗粒度行动类别、生命周期阶段和已观测的进程结果,而不复制原始产品文本或版本专属 subtype 名称。后台调用会先返回 Job id;随后通用作业控制面会送达完成通知,通过 `job_output` 公开同一最终答案或失败状态 detail,并允许 `job_kill` 请求取消。Claude Code 的推理、工具活动、中间消息、stderr、工作区差异、用量信息、产品标识符、工具输入和原始协议载荷均不会复制到父会话。
#### 对 token 的影响
@@ -139,8 +142,8 @@ Claude Code 子级会在一个全新的 SDK query 中接收独立文本任务。
## 已知限制与后续工作
- **每次运行均新建一个 query 和一个进程**:不支持续接、恢复、池化、进度流或产品会话持久化。
- **静态选择实例**:Profile 配置项固定提供方名称与工具绑定;调用无法动态选择提供方,而且每个公开工具都需要唯一的 `toolName`
- **宿主设置有意保持权威**:项目用户设置可以改变模型、工具行为本提供方不提供经过筛选或与宿主环境隔离的生产模式。
- **静态选择实例**Profile 配置项固定提供方名称、可选模型与工具绑定;调用无法动态选择或修改提供方与模型,而且每个公开工具都需要唯一的 `toolName`
- **宿主设置有意保持权威**省略 `model` 时由项目用户设置选择模型;原生设置始终保留其余工具行为本提供方不提供经过筛选或与宿主环境隔离的生产模式。
- **身份验证与账户状态仍由原生机制管理**:Bundle 会提供 CLI,但不会创建账户、登录或改写 Claude 设置;配置与身份验证失败会公开其生命周期阶段与安全的 `unknown` 回退,而不会增加单独的公开分类。
- **委派时必须存在 SDK 平台载荷**:省略 optional dependencies 的安装、不受支持的平台以及缺失或损坏的载荷都会在第一次 query 时失败;不会回退到宿主 CLI。
- **没有人工交互路径**`AskUserQuestion` 被禁用,权限提示会被拒绝,MCP elicitation 会被拒绝,阻塞对话会快速失败而不会挂起。
@@ -48,7 +48,7 @@
},
"dependencies": {
"@anthropic-ai/sdk": "0.93.0",
"@anthropic-ai/claude-agent-sdk": "0.3.220",
"@anthropic-ai/claude-agent-sdk": "0.3.241",
"@deepseek-ai/schemastery": "workspace:^",
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^4.4.3"
@@ -34,10 +34,12 @@ const DEFAULT_PROVIDER_NAME = 'claude-code'
/* jscpd:ignore-start -- sibling product providers intentionally expose
* overlapping deployment-owned fields without adding a shared config owner. */
/** Deployment-owned permission, environment, and process-release settings. */
/** Deployment-owned model, permission, environment, and process-release settings. */
export interface Config {
/** Provider name on `ctx.subagents` (default `claude-code`). */
providerName?: string
/** Native Claude model fixed for this instance; omitted to inherit Claude settings. */
model?: string
/**
* Explicit environment entries layered over the subprocess seam's
* credential-scrubbed parent environment.
@@ -56,13 +58,14 @@ export interface Config {
export const Config: z<Config> = z.object({
providerName: z.string().min(1).default(DEFAULT_PROVIDER_NAME),
model: z.string().min(1),
env: z.dict(z.string()).default({}),
permissionMode: z.union([...CLAUDE_CODE_PERMISSION_MODES])
.default(DEFAULT_CLAUDE_CODE_PERMISSION_MODE),
disposeGraceMs: z.number().default(DEFAULT_DISPOSE_GRACE_MS),
})
type ResolvedConfig = Required<Config>
type ResolvedConfig = Omit<Required<Config>, 'model'> & Pick<Config, 'model'>
/* jscpd:ignore-end */
/* jscpd:ignore-start -- Cordis registration and shared-seam plumbing mirror
@@ -106,6 +109,7 @@ class ClaudeCodeProvider implements SubagentProvider {
}
const spec: ClaudeCodeRunSpec = {
cwd,
...this.config.model === undefined ? {} : { model: this.config.model },
permissionMode: this.config.permissionMode,
env: this.config.env,
disposeGraceMs: this.config.disposeGraceMs,
@@ -124,11 +128,12 @@ class ClaudeCodeProvider implements SubagentProvider {
/**
* Register one Profile-named Claude Code provider.
* @param ctx - context carrying shared subagent and subprocess services.
* @param config - registry name, permission mode, child environment, and disposal grace.
* @param config - registry name, optional model, permission mode, child environment, and disposal grace.
*/
export function apply(ctx: Context, config: Config): void {
const resolved: ResolvedConfig = {
providerName: config.providerName ?? DEFAULT_PROVIDER_NAME,
...config.model === undefined ? {} : { model: config.model },
env: config.env as Record<string, string>,
permissionMode: config.permissionMode ?? DEFAULT_CLAUDE_CODE_PERMISSION_MODE,
disposeGraceMs: config.disposeGraceMs as number,
@@ -58,8 +58,6 @@ const SUPPORTED_UNATTENDED_DIALOG_KINDS = [
'refusal_fallback_prompt',
] satisfies NonNullable<Options['supportedDialogKinds']>
type ClaudeCodeErrorSubtype = Exclude<SDKResultMessage['subtype'], 'success'>
type ClaudeCodeFailureStage =
| 'query-start'
| 'query-run'
@@ -67,10 +65,10 @@ type ClaudeCodeFailureStage =
| 'teardown'
type ClaudeCodeFailureCategory =
| ClaudeCodeErrorSubtype
| 'invalid-success'
| 'missing-result'
| 'process-exit'
| 'limit'
| 'product-error'
| 'invalid-result'
| 'process'
| 'unknown'
interface ClaudeCodeFailureFacts {
@@ -111,13 +109,14 @@ class ClaudeCodeFailure extends Error {
function sdkFailureCategory(
subtype: string,
): ClaudeCodeErrorSubtype | 'unknown' {
): ClaudeCodeFailureCategory {
switch (subtype) {
case 'error_during_execution':
case 'error_max_turns':
case 'error_max_budget_usd':
case 'error_max_structured_output_retries':
return subtype
return 'limit'
case 'error_during_execution':
return 'product-error'
default:
return 'unknown'
}
@@ -150,6 +149,8 @@ function unattendedDiagnostic(
export interface ClaudeCodeRunSpec {
/** Parent Session workspace supplied to the SDK and real CLI. */
readonly cwd: string
/** Profile-selected native model; omitted to preserve Claude settings. */
readonly model?: string
/** Profile-selected native non-interactive permission mode. */
readonly permissionMode: ClaudeCodePermissionMode
/** Explicit deployment/test environment layered after shared scrubbing. */
@@ -217,7 +218,7 @@ export function successfulResult(message: SDKResultMessage): string {
if (message.is_error || message.result.trim().length === 0) {
throw new ClaudeCodeFailure({
stage: 'query-run',
category: 'invalid-success',
category: 'invalid-result',
})
}
return message.result
@@ -249,7 +250,7 @@ export async function consumeClaudeQuery(
if (answer === undefined) {
throw new ClaudeCodeFailure({
stage: 'query-run',
category: 'missing-result',
category: 'invalid-result',
})
}
return {
@@ -318,6 +319,7 @@ export function claudeQueryOptions(
return {
abortController: controller,
cwd: spec.cwd,
...spec.model === undefined ? {} : { model: spec.model },
env: { ...scrubbedParentEnv(), ...spec.env },
persistSession: false,
disallowedTools: spec.permissionMode === 'plan'
@@ -550,7 +552,7 @@ export async function startClaudeCodeRun(
} else if (processOutcome !== undefined && !receivedResult) {
facts = {
stage: 'process',
category: 'process-exit',
category: 'process',
outcome: processOutcome,
}
} else {
@@ -16,11 +16,13 @@
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-primary
model: claude-primary-model
- id: subagent-claude-secondary
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-secondary
model: claude-secondary-model
- id: tool-subagent-codex
name: '@deepseek-ai/dsh-tool-subagent'
@@ -123,13 +123,13 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)(
})
await ctx.plugin(claudeCode, { env, disposeGraceMs: 3_000 })
expect(sdkPackage.version).toBe('0.3.220')
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
expect(sdkPackage.version).toBe('0.3.241')
expect(sdkPackage.claudeCodeVersion).toBe('2.1.241')
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.241')
const version = await execFileAsync(claudeBin, ['--version'], {
env: { ...process.env, ...env },
})
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
expect(version.stdout.trim()).toBe('2.1.241 (Claude Code)')
const nonce = `DSH_CLAUDE_DEEPSEEK_${randomUUID()}`
const parent = {
@@ -251,7 +251,7 @@ async function expectQuiescent(
function expectedFailure(
stage: 'query-run' | 'process',
category: 'error_during_execution' | 'process-exit',
category: 'product-error' | 'process',
outcome: SubprocessOutcome,
): string {
const fields = [
@@ -268,8 +268,8 @@ function expectedObservedFailure(outcome: SubprocessOutcome): string {
return observedSdkMessages.some(message =>
message.type === 'result'
&& message.subtype === 'error_during_execution')
? expectedFailure('query-run', 'error_during_execution', outcome)
: expectedFailure('process', 'process-exit', outcome)
? expectedFailure('query-run', 'product-error', outcome)
: expectedFailure('process', 'process', outcome)
}
function startRequest(
@@ -284,23 +284,23 @@ function startRequest(
})
}
describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220 fixture', {
describe('real Claude Agent SDK 0.3.241 and its distributed Claude Code 2.1.241 fixture', {
timeout: 60_000,
}, () => {
it('inherits host settings and sends the exact task and fake key to local Messages', async () => {
const sentinel = 'REAL_CLAUDE_CODE_SENTINEL_2_1_220'
const sentinel = 'REAL_CLAUDE_CODE_SENTINEL_2_1_237'
const task = 'Return the fixture sentinel exactly.'
const { harness, fixture } = await realHarness({
kind: 'complete',
text: sentinel,
})
expect(sdkPackage.version).toBe('0.3.220')
expect(sdkPackage.claudeCodeVersion).toBe('2.1.220')
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.220')
expect(sdkPackage.version).toBe('0.3.241')
expect(sdkPackage.claudeCodeVersion).toBe('2.1.241')
expect(sdkPackage.optionalDependencies[platformPackage]).toBe('0.3.241')
const version = await execFileAsync(claudeBin, ['--version'], {
env: { ...process.env, ...harness.env },
})
expect(version.stdout.trim()).toBe('2.1.220 (Claude Code)')
expect(version.stdout.trim()).toBe('2.1.241 (Claude Code)')
const run = await startRequest(harness, task)
await expect(run.result).resolves.toEqual({
@@ -313,7 +313,7 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
(message): message is SDKSystemMessage =>
message.type === 'system' && message.subtype === 'init',
)
expect(initMessage?.claude_code_version).toBe('2.1.220')
expect(initMessage?.claude_code_version).toBe('2.1.241')
const spawnedExecutable = harness.spawnSpecs[0]?.argv[0]
expect(spawnedExecutable).toBeDefined()
expect(process.platform === 'win32'
@@ -368,7 +368,7 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
stopReason: 'error',
})
expect(result.diagnostic).toContain(
'product: Claude Code; stage: query-run; category: error_max_turns',
'product: Claude Code; stage: query-run; category: limit',
)
expect(readFileSync(target, 'utf8')).toBe('real-sdk-max-turns')
expect(result.diagnostic).not.toContain(target)
@@ -387,12 +387,14 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
const { ctx, handles, spawnSpecs } = await realRuntime()
const safeFiber = await ctx.plugin(claudeCode, {
providerName: 'claude-safe',
model: 'claude-safe-model',
env: safeInstance.env,
permissionMode: 'dontAsk',
disposeGraceMs: 3_000,
})
const bypassFiber = await ctx.plugin(claudeCode, {
providerName: 'claude-bypass',
model: 'claude-bypass-model',
env: bypassInstance.env,
permissionMode: 'bypassPermissions',
disposeGraceMs: 3_000,
@@ -440,6 +442,8 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
await Promise.all([safeRun.dispose(), bypassRun.dispose()])
expect(safeInstance.fixture.requests).toHaveLength(1)
expect(bypassInstance.fixture.requests).toHaveLength(1)
expect(safeInstance.fixture.requests[0]?.body.model).toBe('claude-safe-model')
expect(bypassInstance.fixture.requests[0]?.body.model).toBe('claude-bypass-model')
expect(safeInstance.fixture.requests[0]?.body.messages)
.not.toEqual(bypassInstance.fixture.requests[0]?.body.messages)
expect(spawnSpecs.map(spec => spec.env?.CLAUDE_CONFIG_DIR).sort())
@@ -541,7 +545,7 @@ describe('real Claude Agent SDK 0.3.220 and its distributed Claude Code 2.1.220
})
expect(fixture.requests).toHaveLength(2)
expect(JSON.stringify(fixture.requests[1]?.body.messages))
.toContain('ExitPlanMode exists but is not enabled in this context')
.toContain('ExitPlanMode is disabled for this session')
await run.dispose()
await expectQuiescent(harness.handles)
})
@@ -59,8 +59,8 @@ 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_AGENT_SDK_VERSION = '0.3.241'
const CLAUDE_CODE_VERSION = '2.1.241'
const CLAUDE_PLATFORM_PACKAGES = [
'@anthropic-ai/claude-agent-sdk-darwin-arm64',
'@anthropic-ai/claude-agent-sdk-darwin-x64',
@@ -480,12 +480,14 @@ describe('task admission and package contracts', () => {
ctx.on('subagent/provider-removed', providerName => void removed.push(providerName))
const safeFiber = await ctx.plugin(claudeCode, {
providerName: 'claude-safe',
model: 'claude-safe-model',
env: { DSH_CLAUDE_INSTANCE: 'safe' },
permissionMode: 'dontAsk',
disposeGraceMs: 11,
})
const bypassFiber = await ctx.plugin(claudeCode, {
providerName: 'claude-bypass',
model: 'claude-bypass-model',
env: { DSH_CLAUDE_INSTANCE: 'bypass' },
permissionMode: 'bypassPermissions',
disposeGraceMs: 29,
@@ -515,10 +517,11 @@ describe('task admission and package contracts', () => {
})
expect(queryOptions.map(options => ({
instance: options.env?.DSH_CLAUDE_INSTANCE,
model: options.model,
permissionMode: options.permissionMode,
}))).toEqual([
{ instance: 'safe', permissionMode: 'dontAsk' },
{ instance: 'bypass', permissionMode: 'bypassPermissions' },
{ instance: 'safe', model: 'claude-safe-model', permissionMode: 'dontAsk' },
{ instance: 'bypass', model: 'claude-bypass-model', permissionMode: 'bypassPermissions' },
])
expect(spawnSpecs.map(spec => ({
instance: spec.env?.DSH_CLAUDE_INSTANCE,
@@ -556,11 +559,14 @@ describe('task admission and package contracts', () => {
await ctx.fiber.dispose()
})
it('accepts only the five fixed non-interactive permission modes', () => {
it('accepts an optional non-empty model and the five fixed permission modes', () => {
expect(claudeCode.Config({}).providerName).toBe('claude-code')
expect(claudeCode.Config({}).model).toBeUndefined()
expect(claudeCode.Config({ providerName: 'claude-safe' }).providerName)
.toBe('claude-safe')
expect(() => claudeCode.Config({ providerName: '' })).toThrow()
expect(claudeCode.Config({ model: 'claude-opus' }).model).toBe('claude-opus')
expect(() => claudeCode.Config({ model: '' })).toThrow()
expect(claudeCode.Config({}).permissionMode)
.toBe(DEFAULT_CLAUDE_CODE_PERMISSION_MODE)
for (const permissionMode of CLAUDE_CODE_PERMISSION_MODES) {
@@ -576,8 +582,22 @@ describe('task admission and package contracts', () => {
const ctx = new Context()
await ctx.plugin(SubagentRuntime)
await ctx.plugin(LocalSubprocessRuntime)
const child = fakeChild()
vi.spyOn(ctx.subprocess, 'spawn').mockReturnValue(child.handle)
queryMock.mockImplementation(({ options }) => {
expect(options).not.toHaveProperty('model')
expect(options.permissionMode).toBe(DEFAULT_CLAUDE_CODE_PERMISSION_MODE)
options.spawnClaudeCodeProcess!(sdkSpawnOptions())
return queryFrom([success('native model answer')])
})
claudeCode.apply(ctx, { env: {}, disposeGraceMs: 3_000 })
expect(ctx.subagents.getProvider('claude-code')).toBeDefined()
const run = await ctx.subagents.start('claude-code', request())
await expect(run.result).resolves.toEqual({
output: [{ type: 'text', text: 'native model answer' }],
stopReason: 'completed',
})
await run.dispose()
await ctx.fiber.dispose()
})
@@ -593,6 +613,7 @@ describe('task admission and package contracts', () => {
const warn = vi.spyOn(ctx.logger, 'warn').mockImplementation(() => {})
await ctx.plugin(claudeCode, {
providerName: 'claude-diagnostic',
model: 'claude-diagnostic-model',
env: {
ANTHROPIC_API_KEY: 'provider-fake-key',
CLAUDE_CONFIG_DIR: '/private/tmp/dsh-claude-code-unit-config',
@@ -665,7 +686,7 @@ describe('task admission and package contracts', () => {
child.stdout.end()
await expect(run.result).resolves.toEqual({
output: [],
diagnostic: expectedFailureDiagnostic('query-run', 'missing-result'),
diagnostic: expectedFailureDiagnostic('query-run', 'invalid-result'),
stopReason: 'error',
})
expect(warn).toHaveBeenCalledWith(
@@ -678,6 +699,8 @@ describe('task admission and package contracts', () => {
expect(queryMock.mock.calls[1]?.[0].options)
.not.toHaveProperty('pathToClaudeCodeExecutable')
expect(queryMock.mock.calls[1]?.[0].options.permissionMode).toBe('auto')
expect(queryMock.mock.calls[1]?.[0].options.model)
.toBe('claude-diagnostic-model')
expect(spawn).toHaveBeenCalledWith(expect.objectContaining({
cwd: process.cwd(),
graceMs: 29,
@@ -840,6 +863,7 @@ describe('query options and result mapping', () => {
const diagnostics: string[] = []
const spec: ClaudeCodeRunSpec = {
cwd: '/workspace',
model: 'claude-explicit-model',
permissionMode: 'acceptEdits',
env: {
HOST_VISIBLE: 'overridden',
@@ -861,6 +885,7 @@ describe('query options and result mapping', () => {
expect(options).toMatchObject({
abortController: controller,
cwd: '/workspace',
model: 'claude-explicit-model',
persistSession: false,
disallowedTools: ['AskUserQuestion'],
permissionMode: 'acceptEdits',
@@ -897,14 +922,14 @@ describe('query options and result mapping', () => {
message: 'enter SECRET_TOKEN',
requestedSchema: { secret: true },
},
{ signal: callbackSignal },
{ signal: callbackSignal, requestId: 'request-2' },
)).resolves.toEqual({ action: 'decline' })
await expect(options.onUserDialog!(
{
dialogKind: 'refusal_fallback_prompt',
payload: { path: '/private/secret.txt', token: 'SECRET_TOKEN' },
},
{ signal: callbackSignal },
{ signal: callbackSignal, requestId: 'request-3' },
)).resolves.toEqual({ behavior: 'cancelled' })
expect(diagnostics).toEqual([
'Claude Code unattended decision (mode: acceptEdits; request: tool permission; decision: denied): the provider does not request human approval',
@@ -936,6 +961,7 @@ describe('query options and result mapping', () => {
spawn: () => child.handle,
}, new AbortController(), () => {}, () => {})
expect(options.permissionMode).toBe(permissionMode)
expect(options).not.toHaveProperty('model')
expect(options.disallowedTools).toEqual(permissionMode === 'plan'
? ['AskUserQuestion', 'ExitPlanMode']
: ['AskUserQuestion'])
@@ -967,23 +993,23 @@ describe('query options and result mapping', () => {
it('accepts only a non-error success with a non-blank final result', () => {
expect(successfulResult(success('exact final'))).toBe('exact final')
expect(() => successfulResult(success('answer', true)))
.toThrow(expectedFailureDiagnostic('query-run', 'invalid-success'))
.toThrow(expectedFailureDiagnostic('query-run', 'invalid-result'))
expect(() => successfulResult(success(' \n ')))
.toThrow(expectedFailureDiagnostic('query-run', 'invalid-success'))
.toThrow(expectedFailureDiagnostic('query-run', 'invalid-result'))
const sdkFailure = () => successfulResult(failure(
'error_during_execution',
['SECRET_TOKEN', '/private/secret.txt'],
))
expect(sdkFailure).toThrow(expectedFailureDiagnostic(
'query-run',
'error_during_execution',
'product-error',
))
expect(sdkFailure).not.toThrow('SECRET_TOKEN')
expect(sdkFailure).not.toThrow('/private/secret.txt')
expect(() => successfulResult(failure(
'error_max_turns',
[],
))).toThrow(expectedFailureDiagnostic('query-run', 'error_max_turns'))
))).toThrow(expectedFailureDiagnostic('query-run', 'limit'))
const unknown = {
type: 'result',
@@ -1009,7 +1035,7 @@ describe('query options and result mapping', () => {
})
await expect(consumeClaudeQuery(
queryFrom([{ type: 'system', subtype: 'init' } as SDKMessage]),
)).rejects.toThrow(expectedFailureDiagnostic('query-run', 'missing-result'))
)).rejects.toThrow(expectedFailureDiagnostic('query-run', 'invalid-result'))
const onPermissionDenied = vi.fn()
await expect(consumeClaudeQuery(queryFrom([
@@ -1047,14 +1073,14 @@ describe('run publication, cancellation, and settlement', () => {
expect(fixture.child.terminate).toHaveBeenCalledOnce()
})
it('flattens every SDK error result without inventing shared stop reasons', async () => {
const subtypes: ErrorSubtype[] = [
'error_during_execution',
'error_max_turns',
'error_max_budget_usd',
'error_max_structured_output_retries',
it('groups SDK errors by parent-action category without changing stop reasons', async () => {
const cases: Array<readonly [ErrorSubtype, string]> = [
['error_during_execution', 'product-error'],
['error_max_turns', 'limit'],
['error_max_budget_usd', 'limit'],
['error_max_structured_output_retries', 'limit'],
]
for (const subtype of subtypes) {
for (const [subtype, category] of cases) {
const fixture = fakeRun([failure(subtype)])
const onError = vi.fn()
const run = await startClaudeCodeRun(
@@ -1063,7 +1089,7 @@ describe('run publication, cancellation, and settlement', () => {
)
await expect(run.result).resolves.toEqual({
output: [],
diagnostic: expectedFailureDiagnostic('query-run', subtype),
diagnostic: expectedFailureDiagnostic('query-run', category),
stopReason: 'error',
})
expect(onError).toHaveBeenCalledWith(
@@ -1083,7 +1109,7 @@ describe('run publication, cancellation, and settlement', () => {
const result = await run.result
expect(result).toEqual({
output: [],
diagnostic: `${expectedFailureDiagnostic('query-run', 'error_during_execution')}\nClaude Code unattended decision (mode: dontAsk; request: tool permission; decision: denied): Claude Code denied the request before an interactive prompt`,
diagnostic: `${expectedFailureDiagnostic('query-run', 'product-error')}\nClaude Code unattended decision (mode: dontAsk; request: tool permission; decision: denied): Claude Code denied the request before an interactive prompt`,
stopReason: 'error',
})
expect(result.diagnostic).not.toContain('SECRET_TOKEN')
@@ -1126,7 +1152,7 @@ describe('run publication, cancellation, and settlement', () => {
output: [],
diagnostic: expectedFailureDiagnostic(
'query-run',
'error_during_execution',
'product-error',
),
stopReason: 'error',
})
@@ -1163,9 +1189,9 @@ describe('run publication, cancellation, and settlement', () => {
it('maps invalid success and missing result to fixed query-run facts', async () => {
for (const [messages, category] of [
[[success('answer', true)], 'invalid-success'],
[[success('')], 'invalid-success'],
[[{ type: 'system', subtype: 'init' } as SDKMessage], 'missing-result'],
[[success('answer', true)], 'invalid-result'],
[[success('')], 'invalid-result'],
[[{ type: 'system', subtype: 'init' } as SDKMessage], 'invalid-result'],
] as const) {
const fixture = fakeRun(messages)
const run = await startClaudeCodeRun(request(), fixture.spec)
@@ -1207,7 +1233,7 @@ describe('run publication, cancellation, and settlement', () => {
output: [],
diagnostic: expectedFailureDiagnostic(
'process',
'process-exit',
'process',
outcome,
),
stopReason: 'error',
@@ -14,12 +14,12 @@ export const inject = ['subagents']
const RESULTS = [
{
id: '00000000-0000-4000-8000-0000000000d1',
diagnostic: 'Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)',
diagnostic: 'Product subagent failure (product: Claude Code; stage: query-run; category: limit)',
output: [{ type: 'text' as const, text: 'partial assistant text' }],
},
{
id: '00000000-0000-4000-8000-0000000000d2',
diagnostic: 'Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)',
diagnostic: 'Product subagent failure (product: Claude Code; stage: query-run; category: limit)',
output: [],
},
{
+37 -37
View File
@@ -8171,8 +8171,8 @@ importers:
packages/subagent/subagent-claude-code:
dependencies:
'@anthropic-ai/claude-agent-sdk':
specifier: 0.3.220
version: 0.3.220(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)
specifier: 0.3.241
version: 0.3.241(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)
'@anthropic-ai/sdk':
specifier: 0.93.0
version: 0.93.0(zod@4.4.3)
@@ -10287,52 +10287,52 @@ packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.220':
resolution: {integrity: sha512-7VxlbEosK7DODiOnsjoVd0DSJzbnaPrM2jelMHI0y8zx1UnLS3WC6EFUXbvy74F2sXqEznh2tzn7EKWInaRN6Q==}
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.241':
resolution: {integrity: sha512-v26ta54lKFMFEZzbOE+6p3YhKERWnDiEA6OmkSAg+3fAQHOa1+aLTKw222cfgzxgiVixwFtHMk8c63zsDd8aXQ==}
cpu: [arm64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.220':
resolution: {integrity: sha512-X9RwDsSmbF6ultKZroaip+DL8WRgC64gHbrAwrRlAFSPNZV7zmJyP2ur8rW7KrxqmtuehdMMkw8+SAC/6hD2PA==}
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.241':
resolution: {integrity: sha512-5jweT0vft1ZCaGSoxZHF9vJlHbx8Yxx4+x5aHAIXTd4lx7ZbT4o5buEF8kpmTeHUB+Fw9jtFIm4QDsRiBXgf+Q==}
cpu: [x64]
os: [darwin]
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.220':
resolution: {integrity: sha512-OHoZOZ8Cf2TBr6oXIXPwyvUxj9jrq2w8E4poA8dMpacXszcPSPiCQCMuuOh4aWJzfeJE1+TtWxhKMVb2csXyZQ==}
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.241':
resolution: {integrity: sha512-GslvPvSzehfCZyzOaJAt4lgodznm5zpl/LMXN8ygD12z5qnpM+I9/eFnmAaISJ0L8/vyohtlAP1jjaeR2jz1AQ==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.220':
resolution: {integrity: sha512-WkROPwWskqhKR9XgnmseHQ6rLi9zM9qt57IWoToIjL/eXOqDWipp7JXZ1L5ud+LrA42dunHPZfBwD/vXZ+A7LA==}
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.241':
resolution: {integrity: sha512-SxszQGffXiLzMEnAv+pJXEmQbA8haijKyRjjH/jOt1CLeMIfpjKcO9WQDv8dEA8nREWS3zJ103zjgecAF7oOQQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.220':
resolution: {integrity: sha512-K+FWj+LcGhC1Z7wqeWoLxm1iemcba5xKpLLFVwYm4V6HyMx3ruYd/2r2TiQtjT+JWeNFWIys0ScHiItR6vWAiA==}
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.241':
resolution: {integrity: sha512-kZigJ5Ug2I2G/n7Cunmwy4TGr0lOGnWrz6TkzyWiDcUmJOodoTH6GZECNarWAtETfN03AAeLfrpiz8z3hOEDqA==}
cpu: [x64]
os: [linux]
libc: [musl]
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.220':
resolution: {integrity: sha512-tkTJFnpR9VifvWX2fmkCAPkT6+8Wk/gVu8B5jsVekKZPiZoWRHmMXO30BnZn+f0TZhgYP+82PSX3S8crH1kn+w==}
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.241':
resolution: {integrity: sha512-gJRa922Qcm7loumHcXMDFEFg//tz1aOi7Nx0sQa9I9lC1JSN8yL6i7/idzOU5Hp193tEDFOgqIMFL/yRiXg+rw==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.220':
resolution: {integrity: sha512-rIwgq0UwQExWl6KrHUyC4w5KwpL9l6nd95aUTx6RitexaAuEw//xtfTVLnuE4hDDQZFkzEwpdKc3nxDWoGcUbA==}
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.241':
resolution: {integrity: sha512-/3yA9jQuCvHDVlILzhtslH6kFYOvydXyMZiKwnzqM8ZfvFTNO41w8TpiFpBLseyM+4A4E8QMeTKu3L01Xyb5IQ==}
cpu: [arm64]
os: [win32]
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.220':
resolution: {integrity: sha512-MuOuXhbr66HlGaWXD2f3w0k2PsvmnbkwcUZ0dAe2poFLdl72GC2dapwwOBefxm9QmoNqk9+jmv/dSKGOVWyvLw==}
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.241':
resolution: {integrity: sha512-cHYdAgORl9kynujMeYXyV1uj/hbmsBjRw9dRVkIW4/4sF7S6L4u/qDSzn1/wiNP7g2yWSJ4KbsvHDH2WWDnCBQ==}
cpu: [x64]
os: [win32]
'@anthropic-ai/claude-agent-sdk@0.3.220':
resolution: {integrity: sha512-glc7SdwPkOkLw8oxwLo9PKTdLJGqW/PIR4urWXFoRtX9YllwozsEVc5Tc1+EvLSkfrsxPJqQWqOgpjUOQXf1oA==}
'@anthropic-ai/claude-agent-sdk@0.3.241':
resolution: {integrity: sha512-pIHdCSTywFe30H0oWDCKZzC4ipBLtF5YMDRKjf6PHyARg57O4l/72v3b6QKnnefwtKKMe6uWJ1Y9lUJg/sKWyA==}
engines: {node: '>=18.0.0'}
peerDependencies:
'@anthropic-ai/sdk': '>=0.93.0'
@@ -16064,44 +16064,44 @@ snapshots:
package-manager-detector: 1.6.0
tinyexec: 1.2.4
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.220':
'@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.220':
'@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.220':
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.220':
'@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.220':
'@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.220':
'@anthropic-ai/claude-agent-sdk-linux-x64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.220':
'@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.220':
'@anthropic-ai/claude-agent-sdk-win32-x64@0.3.241':
optional: true
'@anthropic-ai/claude-agent-sdk@0.3.220(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)':
'@anthropic-ai/claude-agent-sdk@0.3.241(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)':
dependencies:
'@anthropic-ai/sdk': 0.93.0(zod@4.4.3)
'@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3)
zod: 4.4.3
optionalDependencies:
'@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.220
'@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.220
'@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.220
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.220
'@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.220
'@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.220
'@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.220
'@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.220
'@anthropic-ai/claude-agent-sdk-darwin-arm64': 0.3.241
'@anthropic-ai/claude-agent-sdk-darwin-x64': 0.3.241
'@anthropic-ai/claude-agent-sdk-linux-arm64': 0.3.241
'@anthropic-ai/claude-agent-sdk-linux-arm64-musl': 0.3.241
'@anthropic-ai/claude-agent-sdk-linux-x64': 0.3.241
'@anthropic-ai/claude-agent-sdk-linux-x64-musl': 0.3.241
'@anthropic-ai/claude-agent-sdk-win32-arm64': 0.3.241
'@anthropic-ai/claude-agent-sdk-win32-x64': 0.3.241
'@anthropic-ai/sdk@0.91.1(zod@4.4.3)':
dependencies:
+11
View File
@@ -59,6 +59,17 @@ minimumReleaseAgeExclude:
- node-addon-require-builtin-win32-ia32-msvc@0.1.4
- node-addon-require-builtin-win32-x64-msvc@0.1.4
- node-addon-require-builtin@0.1.4
# The active pnpm supply-chain policy blocks this reviewed runtime closure
# until its release-age window expires unless every exact package is exempt.
- '@anthropic-ai/claude-agent-sdk-darwin-arm64@0.3.241'
- '@anthropic-ai/claude-agent-sdk-darwin-x64@0.3.241'
- '@anthropic-ai/claude-agent-sdk-linux-arm64-musl@0.3.241'
- '@anthropic-ai/claude-agent-sdk-linux-arm64@0.3.241'
- '@anthropic-ai/claude-agent-sdk-linux-x64-musl@0.3.241'
- '@anthropic-ai/claude-agent-sdk-linux-x64@0.3.241'
- '@anthropic-ai/claude-agent-sdk-win32-arm64@0.3.241'
- '@anthropic-ai/claude-agent-sdk-win32-x64@0.3.241'
- '@anthropic-ai/claude-agent-sdk@0.3.241'
patchedDependencies:
node-pty@1.2.0-beta.15: patches/node-pty@1.2.0-beta.15.patch
@@ -26,10 +26,12 @@
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-primary
model: claude-primary-model
- id: subagent-claude-secondary
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-secondary
model: claude-secondary-model
- id: tool-subagent-codex-primary
name: '@deepseek-ai/dsh-tool-subagent'
config:
@@ -14,10 +14,12 @@
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-primary
model: claude-primary-model
- id: subagent-claude-secondary
name: '@deepseek-ai/dsh-subagent-claude-code'
config:
providerName: claude-secondary
model: claude-secondary-model
- id: tool-subagent-codex-primary
name: '@deepseek-ai/dsh-tool-subagent'
config:
@@ -18,7 +18,7 @@
{"type":"assistant/chunk","data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_claude_foreground","name":"subagent_codex","arguments":"{\"description\":\"Observe Claude foreground diagnostic\",\"prompt\":\"Return the Claude diagnostic failure.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:3}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[12,13,14,15,16],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_claude_foreground","name":"subagent_codex","arguments":"{\"description\":\"Observe Claude foreground diagnostic\",\"prompt\":\"Return the Claude diagnostic failure.\",\"run_in_background\":false}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_claude_foreground"},"content":[{"type":"tool-result","toolCallId":"call_claude_foreground","content":[{"type":"text","text":"Error: subagent run failed\nDiagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)\nPartial output before the run ended:\npartial assistant text"}],"isError":true}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_claude_foreground"},"content":[{"type":"tool-result","toolCallId":"call_claude_foreground","content":[{"type":"text","text":"Error: subagent run failed\nDiagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: limit)\nPartial output before the run ended:\npartial assistant text"}],"isError":true}],"role":"user","id":"{{message:4}}"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
@@ -28,12 +28,12 @@
{"type":"assistant/chunk","data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_claude_background","name":"subagent_codex","arguments":"{\"description\":\"Observe Claude background diagnostic\",\"prompt\":\"Return the Claude diagnostic failure.\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:5}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[22,23,24,25,26],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_claude_background","name":"subagent_codex","arguments":"{\"description\":\"Observe Claude background diagnostic\",\"prompt\":\"Return the Claude diagnostic failure.\",\"run_in_background\":true}"}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job subagent-1 (subagent: Observe Claude background diagnostic) finished [status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)]. Read its output with job_output."}],"source":{"kind":"plugin","plugin":"tool-jobs","form":"notice","summary":"subagent Observe Claude background diagnostic [status: failed, error; diagnostic: Product subagent failure (product: Cl…"},"role":"user","id":"{{message:6}}"}]}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job subagent-1 (subagent: Observe Claude background diagnostic) finished [status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: limit)]. Read its output with job_output."}],"source":{"kind":"plugin","plugin":"tool-jobs","form":"notice","summary":"subagent Observe Claude background diagnostic [status: failed, error; diagnostic: Product subagent failure (product: Cl…"},"role":"user","id":"{{message:6}}"}]}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_claude_background"},"content":[{"type":"tool-result","toolCallId":"call_claude_background","content":[{"type":"text","text":"started background subagent job subagent-1"}],"isError":false}],"role":"user","id":"{{message:7}}"}},"sourceEventSeqs":[28],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":1,"step":3}}
{"type":"user/message","data":{"content":[{"type":"text","text":"background job subagent-1 (subagent: Observe Claude background diagnostic) finished [status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)]. Read its output with job_output."}],"source":{"kind":"plugin","plugin":"tool-jobs","form":"notice","summary":"subagent Observe Claude background diagnostic [status: failed, error; diagnostic: Product subagent failure (product: Cl…"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"background job subagent-1 (subagent: Observe Claude background diagnostic) finished [status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: limit)]. Read its output with job_output."}],"source":{"kind":"plugin","plugin":"tool-jobs","form":"notice","summary":"subagent Observe Claude background diagnostic [status: failed, error; diagnostic: Product subagent failure (product: Cl…"},"role":"user","id":"{{message:6}}"},"surfaceOp":"append"}
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"call_claude_output","name":"job_output","argumentsDelta":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}}
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_claude_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}}}
@@ -41,7 +41,7 @@
{"type":"assistant/chunk","data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_claude_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":3,"callId":"call_claude_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_claude_output"},"content":[{"type":"tool-result","toolCallId":"call_claude_output","content":[{"type":"text","text":"(no new output)\n[status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: error_max_budget_usd)]"}],"isError":false}],"role":"user","id":"{{message:9}}"}},"sourceEventSeqs":[41],"surfaceOp":"append"}
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_claude_output"},"content":[{"type":"tool-result","toolCallId":"call_claude_output","content":[{"type":"text","text":"(no new output)\n[status: failed, error; diagnostic: Product subagent failure (product: Claude Code; stage: query-run; category: limit)]"}],"isError":false}],"role":"user","id":"{{message:9}}"}},"sourceEventSeqs":[41],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":3}}
{"type":"step/start","data":{"turn":1,"step":4}}
{"type":"assistant/chunk","data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}