feat(agent-presets): enable background Codex and Claude Code subagent tasks

This commit is contained in:
pku-xht
2026-08-12 17:07:34 +08:00
parent b423ed15d7
commit 28fcda2751
35 changed files with 318 additions and 102 deletions

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 packages/subagent/subagent-claude-code/README.md
README.md: 17b14e847baea3eadda7129b5e49f5e65b668cc8
README.zh.md: 2f59144d5bd9f26a58773e6dd53909b2b0e8da14
README.md: 556cde7da4467659d02709c43325e60fa7de6a74
README.zh.md: bda00d7be4467e739604eeba0020850cb1a99d50

View File

@@ -31,7 +31,7 @@ The provider advertises no optional start-time capabilities and reports `inherit
Production resolves `claude` from the subprocess execution world's credential-scrubbed `PATH`, with explicit `env` entries applied, and passes the resulting path to the SDK as `pathToClaudeCodeExecutable`. On Windows, a resolved `.cmd` or `.bat` path is carried as a quoted, per-spawn environment value that `cmd.exe /v:off` expands once, so valid path metacharacters remain data. The pinned SDK's fixed flags then occupy cmd's command tail and contain no cmd metacharacters; they are not ordinary Windows argv. Native settings and authentication remain authoritative. The plugin does not install another CLI, select a model, create a product home, log in, or probe an account. Credential-shaped ambient variables are removed before the explicit `env` overlay is applied, so an API key or token intended for the child must be supplied there. Non-credential endpoint variables such as `ANTHROPIC_BASE_URL`, along with ordinary ambient values such as `PATH` and `HOME`, remain inherited unless overridden.
Shipped profiles load this provider once on the host and start no Claude process until a tool call. Full Agent Presets carry the tool row below with `disabled: true`; copy a preset and remove that field to expose `subagent_claude_code` only to agents composed from the copy. A custom host composition can still use both rows directly.
Shipped profiles load this provider once on the host and start no Claude process until a tool call. Full Agent Presets carry the tool row below 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 Task id for `task_output` or `task_kill`. Shipped full profiles already provide the task registry and controls; a custom composition must load the same generic task provider and consumer when it enables this background route.
```yaml
- id: subagent-claude-code
@@ -40,13 +40,19 @@ Shipped profiles load this provider once on the host and start no Claude process
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
- id: tasks
name: '@deepseek-ai/dsh-tasks-local'
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
- id: tool-subagent-claude-code
name: '@deepseek-ai/dsh-tool-subagent'
disabled: true
config:
provider: claude-code
toolName: subagent_claude_code
enableRunInBackground: false
backgroundMode: one-shot
maxDepth: provider-managed
```
@@ -72,19 +78,19 @@ The child pays for an independent Claude Code context and query. Child tokens do
Independent of the parent request cache. Reuse depends only on Claude Code's own model, instructions, tools, native settings, and fresh query.
### Parent tool result, indirectly
### Parent scheduling and results, indirectly
#### What the model sees
Through `dsh-tool-subagent`, the parent sees only the strict final Claude Code answer or the consumer's exact error for a non-completed result. Claude Code reasoning, tool activity, intermediate messages, stderr, workspace diffs, usage, and product ids are not copied into the parent Session.
Through `dsh-tool-subagent`, a foreground call gives the parent the strict final Claude Code answer or the consumer's exact error for a non-completed result. A background call first returns a Task id; the generic task controls later deliver a bounded completion notice, expose the final answer and status through `task_output`, and let `task_kill` request cancellation. Claude Code reasoning, tool activity, intermediate messages, stderr, workspace diffs, usage, and product ids are not copied into the parent Session.
#### Token effect
Parent input grows only by the final answer or error retained in the tool result. This provider adds no parent tool schema by itself.
Foreground input grows by the retained final answer or error. Background input also includes the start acknowledgement, completion notice, and any `task_output`, `task_kill`, or later status results; child tokens still do not enter the parent context. This provider adds no parent tool schema by itself.
#### KV Cache effect
Append-only: the new tool result follows the reusable parent request prefix.
Append-only: foreground adds one result after the reusable parent prefix, while background appends the Task acknowledgement, notice, and later control or collection results. Background scheduling can add a notice-driven turn, but none of these messages rewrites the earlier prefix.
## Known Limitations and Deferred Work
@@ -93,6 +99,6 @@ Append-only: the new tool result follows the reusable parent request prefix.
- **Product installation and account state remain native** — a missing or incompatible `claude`, configuration error, or authentication failure is surfaced as a startup or run error; the plugin provides no installer or login flow.
- **The SDK platform CLI remains in the install closure** — production ignores it in favor of the host `claude`, but the current SDK optional dependency is still installed and supplies the keyless compatibility fixture. Removing that payload belongs to the separate product installation-closure follow-up.
- **No human interaction path** — `AskUserQuestion` is disabled and other interactive callbacks are absent, so tasks requiring new approval or input fail instead of suspending.
- **Final text only** — reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain product-local.
- **Product payload is final text only** — reasoning, intermediate messages, tool traffic, usage, stderr, and workspace diffs remain product-local; generic Task ids, notices, and status come from the shared task runtime.
- **No optional shared capabilities** — output schemas, child personas, tool filtering, and harness depth enforcement are rejected by the shared service for this provider.
- **No wall-clock timeout or side-effect rollback** — the caller cancels long work, and files or external systems changed before cancellation are not restored.

View File

@@ -31,7 +31,7 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
生产环境从子进程执行世界清除凭证后的 `PATH` 解析 `claude`,再应用显式 `env` 条目,并把所得路径作为 `pathToClaudeCodeExecutable` 交给 SDK。在 Windows 上,解析到的 `.cmd``.bat` 路径会作为带引号、仅供本次 spawn 使用的环境值交给 `cmd.exe /v:off` 展开一次,因此合法路径中的元字符仍只是数据。锁定版本的 SDK 随后把固定命令行选项放在 cmd 的命令尾部;这些选项不含 cmd 元字符,也并不是普通的 Windows argv。原生设置与身份验证继续是权威来源。本插件不安装另一份 CLI、不选择模型、不创建产品主目录、不执行登录也不探测账户。具有凭证特征的环境变量会在显式 `env` 覆盖生效前被清除,因此供子进程使用的 API 密钥或 token 必须在该配置中显式提供。除非被覆盖,`ANTHROPIC_BASE_URL` 等非凭证端点变量以及 `PATH``HOME` 等普通环境变量仍会被继承。
随附 profile 会在宿主上加载一次该提供方,而且在工具被调用前不会启动 Claude 进程。完整 Agent Preset 携带下列工具行并设置 `disabled: true`;复制一个 preset 后删除该字段,即可只向由该副本组装的 agent 暴露 `subagent_claude_code`自定义宿主组装仍可直接使用两条配置行
随附 profile 会在宿主上加载一次该提供方,而且在工具被调用前不会启动 Claude 进程。完整 Agent Preset 携带下列工具行并设置 `disabled: true`;复制一个 preset 后删除该字段,即可只向由该副本组装的 agent 暴露 `subagent_claude_code``one-shot` 策略会让省略 `run_in_background` 或传入 `false` 的调用继续在前台等待,而显式传入 `true` 会返回由父 agent 拥有的 Task ID`task_output``task_kill` 使用。随附完整 profile 已提供任务注册表和控制工具;自定义组装若启用该后台路径,也必须加载同一通用任务提供方与消费方
```yaml
- id: subagent-claude-code
@@ -40,13 +40,19 @@ SDK 接收由文本块原样拼接成的任务。提供方会完整迭代 SDK
env:
ANTHROPIC_API_KEY: !!js process.env.ANTHROPIC_API_KEY
- id: tasks
name: '@deepseek-ai/dsh-tasks-local'
- id: tool-tasks
name: '@deepseek-ai/dsh-tool-tasks'
- id: tool-subagent-claude-code
name: '@deepseek-ai/dsh-tool-subagent'
disabled: true
config:
provider: claude-code
toolName: subagent_claude_code
enableRunInBackground: false
backgroundMode: one-shot
maxDepth: provider-managed
```
@@ -72,19 +78,19 @@ Claude Code 子任务会在一个全新的 SDK query 中接收独立文本任务
这与父请求缓存相互独立。能否复用只取决于 Claude Code 自身的模型、指令、工具、原生设置和全新 query。
### 父级工具结果(间接)
### 父级调度与结果(间接)
#### 模型看到的内容
通过 `dsh-tool-subagent`,父级模型只会看到符合严格成功条件的 Claude Code 最终答案或者在结果未完成时看到消费方给出的原样错误。Claude Code 的推理、工具活动、中间消息、stderr、工作区差异、用量信息和产品标识符均不会复制到父会话。
通过 `dsh-tool-subagent`前台调用会让父级模型看到符合严格成功条件的 Claude Code 最终答案,或者在结果未完成时看到消费方给出的原样错误。后台调用会先返回 Task id随后通用任务控制面会送达有界完成通知通过 `task_output` 公开最终答案与状态,并允许 `task_kill` 请求取消。Claude Code 的推理、工具活动、中间消息、stderr、工作区差异、用量信息和产品标识符均不会复制到父会话。
#### 对 token 的影响
父级输入会增加工具结果中保留的最终答案或错误内容。本提供方自身不添加父级工具 schema。
前台输入会增加工具结果中保留的最终答案或错误内容。后台输入还会包含启动确认、完成通知,以及 `task_output``task_kill` 或后续状态结果;子任务 token 仍不会进入父级上下文。本提供方自身不添加父级工具 schema。
#### 对 KV Cache 的影响
仅追加:新的工具结果接在可复用的父请求前缀后。
仅追加:前台会在可复用的父请求前缀后增加一个结果,后台则会继续追加 Task 启动确认、通知以及后续控制或收集结果。后台调度可能增加一个由通知唤醒的轮次,但这些消息都不会改写更早的前缀
## 已知限制与后续工作
@@ -93,6 +99,6 @@ Claude Code 子任务会在一个全新的 SDK query 中接收独立文本任务
- **产品安装与账户状态仍由原生机制管理**`claude` 缺失或不兼容、配置错误或身份验证失败都会呈现为启动错误或运行错误;本插件不提供安装程序或登录流程。
- **SDK 平台 CLI 仍在安装闭包内**:生产环境会忽略它,改用宿主提供的 `claude`,但当前 SDK 的可选依赖仍会安装,并提供无密钥兼容性 fixture。移除该载荷属于独立的产品安装闭包后续项。
- **没有人工交互路径**`AskUserQuestion` 被禁用,其他交互回调也不存在,因此需要新审批或输入的任务会失败而不会挂起。
- **仅返回最终文本**推理、中间消息、工具通信、用量信息、stderr 和工作区差异仍只保留在产品内部。
- **产品载荷仅包含最终文本**推理、中间消息、工具通信、用量信息、stderr 和工作区差异仍只保留在产品内部;通用 Task id、通知与状态来自共享任务运行时
- **没有可选的共享能力**:对于本提供方,共享服务会拒绝输出 schema、子任务角色设定、工具筛选和 harness 深度强制约束。
- **没有按实际经过时间触发的超时或副作用回滚**:长时间运行的工作由调用方取消,且取消前已更改的文件或外部系统不会恢复原状。

View File

@@ -15,7 +15,7 @@ const configPath = join(fixtureDir, 'cordis.yml')
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
describe('product-provider public Loader composition', () => {
it('loads both opt-in packages and foreground tools without starting either product', async () => {
it('loads both opt-in packages, one-shot task tools, and task controls without starting either product', async () => {
const { stdout, stderr } = await runLoaderSmoke({
label: 'product-provider Loader composition',
tempDirPrefix: 'dsh-product-provider-loader-',
@@ -57,15 +57,16 @@ describe('product-provider public Loader composition', () => {
tools: [
{
name: 'subagent_codex',
parameterNames: ['description', 'prompt'],
parameterNames: ['description', 'prompt', 'run_in_background'],
required: ['description', 'prompt'],
},
{
name: 'subagent_claude_code',
parameterNames: ['description', 'prompt'],
parameterNames: ['description', 'prompt', 'run_in_background'],
required: ['description', 'prompt'],
},
],
taskTools: ['task_kill', 'task_list', 'task_output'],
starts: 0,
})
}, LOADER_SMOKE_TEST_TIMEOUT_MS)