diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 34cb543303..642a4d719a 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -884,4 +884,4 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. - `@deepseek-ai/dsh-brand` ([`packages/util/brand/src/index.ts`](../packages/util/brand/src/index.ts)) - `@deepseek-ai/dsh-hook-protocol` ([`packages/hooks/hook-protocol/src/index.ts`](../packages/hooks/hook-protocol/src/index.ts)) - `@deepseek-ai/dsh-subagent-inprocess` ([`packages/subagent/subagent-inprocess/src/index.ts`](../packages/subagent/subagent-inprocess/src/index.ts)) -- `@deepseek-ai/dsh-subagent-process` ([`packages/subagent/subagent-process/src/index.ts`](../packages/subagent/subagent-process/src/index.ts)) +- `@deepseek-ai/dsh-subagent-subprocess` ([`packages/subagent/subagent-subprocess/src/index.ts`](../packages/subagent/subagent-subprocess/src/index.ts)) diff --git a/docs/module-graph.md b/docs/module-graph.md index 57fb397e54..c0e64c1d36 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -43,8 +43,8 @@ flowchart TD pkg_subagent_acp["subagent-acp"] pkg_subagent_fork["subagent-fork"] pkg_subagent_inprocess["subagent-inprocess"] - pkg_subagent_process["subagent-process"] pkg_subagent_spawn["subagent-spawn"] + pkg_subagent_subprocess["subagent-subprocess"] pkg_tool_subagent["tool-subagent"] end subgraph group_web["packages/web"] @@ -179,7 +179,7 @@ flowchart TD pkg_subagent_acp --> pkg_agent pkg_subagent_acp --> pkg_llm pkg_subagent_acp --> pkg_subagent - pkg_subagent_acp --> pkg_subagent_process + pkg_subagent_acp --> pkg_subagent_subprocess pkg_subagent_inprocess --> pkg_agent pkg_subagent_inprocess --> pkg_llm pkg_subagent_inprocess --> pkg_session @@ -220,7 +220,7 @@ flowchart TD | Package | Group | Depends on | | --- | --- | --- | | [`brand`](../packages/util/brand) | `util` | — | -| [`subagent-process`](../packages/subagent/subagent-process) | `subagent` | — | +| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | — | | [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | — | | [`app-boot`](../packages/ui/app-boot) | `ui` | — | | [`code-runtime`](../packages/code-runtime/code-runtime) | `code-runtime` | — | @@ -260,7 +260,7 @@ flowchart TD | [`acp`](../packages/ui/acp) | `ui` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`tools`](../packages/core/tools) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`tools`](../packages/core/tools) | | [`agent-core`](../packages/core/agent-core) | `core` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tool-bash`](../packages/bash/tool-bash), [`tools`](../packages/core/tools) | -| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`subagent-process`](../packages/subagent/subagent-process) | +| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | | [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | diff --git a/knip.json b/knip.json index d4c44e9aa5..ecfd431c1c 100644 --- a/knip.json +++ b/knip.json @@ -70,7 +70,7 @@ "entry": ["tests/**/*.spec.ts", "tests/**/*.e2e.ts", "tests/mock-acp-server.ts"], "project": ["src/**/*.ts", "tests/**/*.ts"] }, - "packages/subagent/subagent-process": { + "packages/subagent/subagent-subprocess": { "entry": ["tests/**/*.spec.ts"], "project": ["src/**/*.ts", "tests/**/*.ts"], "ignoreDependencies": ["cordis"] diff --git a/packages/subagent/README.md b/packages/subagent/README.md index 38c64d22a5..87930167e9 100644 --- a/packages/subagent/README.md +++ b/packages/subagent/README.md @@ -8,10 +8,10 @@ The subagent seam: an agent delegating work to a child agent. Like the [bash](.. | `subagent-inprocess/` | Shared in-process run driver (pure lib; registers nothing) | — | | `subagent-spawn/` | In-process backend: a fresh child agent | (registers on `ctx.subagents`) | | `subagent-fork/` | In-process backend: a child seeded with the parent's completed-turn prefix | (registers on `ctx.subagents`) | -| `subagent-process/` | Shared out-of-process machinery: env scrub, dispose ladder, isolated config dirs (pure lib; registers nothing) | — | +| `subagent-subprocess/` | Shared out-of-process machinery: env scrub, dispose ladder, isolated config dirs (pure lib; registers nothing) | — | | `subagent-acp/` | Out-of-process backend: a child agent in a spawned subprocess, driven over ACP | (registers on `ctx.subagents`) | | `tool-subagent/` | Model-facing `subagent` delegation tool over `ctx.subagents` | (registers on `ctx.tools`) | -The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` backends share the `subagent-inprocess` driver (a pure library — both depend on it, neither on the other), the out-of-process `subagent-acp` backend builds on the `subagent-process` library (the credential env scrub, the dispose ladder, isolated config dirs) and ships alongside them here; the test-only `dsh-subagent-mock` (in [support](../support/README.md)) is separate. All **product** packages except the mock. +The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` backends share the `subagent-inprocess` driver (a pure library — both depend on it, neither on the other), the out-of-process `subagent-acp` backend builds on the `subagent-subprocess` library (the credential env scrub, the dispose ladder, isolated config dirs) and ships alongside them here; the test-only `dsh-subagent-mock` (in [support](../support/README.md)) is separate. All **product** packages except the mock. The proposal and design rationale: [docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md](../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md). diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 2cc330aefd..7fb087dcdc 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -57,7 +57,7 @@ A spawn/transport/RPC failure resolves `error` (or `aborted` if a cancel was req ## Environment scrub -The child env is built by [`buildChildEnv` from `@deepseek-ai/dsh-subagent-process`](../subagent-process/README.md) — the ambient env minus credential-shaped vars, with `config.env` layered on top after the scrub; the pattern and full semantics live there. For this backend that means the parent harness's own secrets never leak into the spawned agent implicitly, while the child's OWN `DEEPSEEK_API_KEY` is supplied deliberately via `config.env` and survives. +The child env is built by [`buildChildEnv` from `@deepseek-ai/dsh-subagent-subprocess`](../subagent-subprocess/README.md) — the ambient env minus credential-shaped vars, with `config.env` layered on top after the scrub; the pattern and full semantics live there. For this backend that means the parent harness's own secrets never leak into the spawned agent implicitly, while the child's OWN `DEEPSEEK_API_KEY` is supplied deliberately via `config.env` and survives. ## Testing diff --git a/packages/subagent/subagent-acp/package.json b/packages/subagent/subagent-acp/package.json index 45fc6b072c..e73d861a79 100644 --- a/packages/subagent/subagent-acp/package.json +++ b/packages/subagent/subagent-acp/package.json @@ -25,7 +25,7 @@ "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-llm": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", - "@deepseek-ai/dsh-subagent-process": "^0.0.1", + "@deepseek-ai/dsh-subagent-subprocess": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "dependencies": { @@ -36,7 +36,7 @@ "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", - "@deepseek-ai/dsh-subagent-process": "workspace:^", + "@deepseek-ai/dsh-subagent-subprocess": "workspace:^", "@cordisjs/plugin-loader": "^1.0.0-rc.4", "cordis": "^4.0.0-rc.6" } diff --git a/packages/subagent/subagent-acp/src/run.ts b/packages/subagent/subagent-acp/src/run.ts index a94c923f46..a9fefba27c 100644 --- a/packages/subagent/subagent-acp/src/run.ts +++ b/packages/subagent/subagent-acp/src/run.ts @@ -40,7 +40,7 @@ import { import { AgentId } from '@deepseek-ai/dsh-agent' import type { ContentBlock } from '@deepseek-ai/dsh-llm' import type { SubagentResult, SubagentRun, SubagentStartRequest, SubagentStopReason } from '@deepseek-ai/dsh-subagent' -import { buildChildEnv, disposeChildProcess, spawnFailure } from '@deepseek-ai/dsh-subagent-process' +import { buildChildEnv, disposeChildProcess, spawnFailure } from '@deepseek-ai/dsh-subagent-subprocess' /** * How the client answers a child's `session/request_permission`. The first cut diff --git a/packages/subagent/subagent-acp/tests/subagent-acp.spec.ts b/packages/subagent/subagent-acp/tests/subagent-acp.spec.ts index 6d351c71d0..92c025077a 100644 --- a/packages/subagent/subagent-acp/tests/subagent-acp.spec.ts +++ b/packages/subagent/subagent-acp/tests/subagent-acp.spec.ts @@ -6,7 +6,7 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import SubagentService from '@deepseek-ai/dsh-subagent' -import { buildChildEnv, SENSITIVE_ENV_PATTERN } from '@deepseek-ai/dsh-subagent-process' +import { buildChildEnv, SENSITIVE_ENV_PATTERN } from '@deepseek-ai/dsh-subagent-subprocess' import type { Agent } from '@deepseek-ai/dsh-agent' import * as acp from '../src/index.ts' import { acpStopReason, acpContentText, DEFAULT_DISPOSE_EOF_GRACE_MS, DEFAULT_DISPOSE_GRACE_MS, startAcpRun, toAcpPrompt, type AcpRunSpec } from '../src/run.ts' diff --git a/packages/subagent/subagent-acp/tsconfig.json b/packages/subagent/subagent-acp/tsconfig.json index ab24f60f93..e415ace1de 100644 --- a/packages/subagent/subagent-acp/tsconfig.json +++ b/packages/subagent/subagent-acp/tsconfig.json @@ -27,7 +27,7 @@ "path": "../subagent" }, { - "path": "../subagent-process" + "path": "../subagent-subprocess" } ] } diff --git a/packages/subagent/subagent-process/README.md b/packages/subagent/subagent-subprocess/README.md similarity index 86% rename from packages/subagent/subagent-process/README.md rename to packages/subagent/subagent-subprocess/README.md index e6c8e10a96..ccc68bf31b 100644 --- a/packages/subagent/subagent-process/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -1,4 +1,4 @@ -# @deepseek-ai/dsh-subagent-process +# @deepseek-ai/dsh-subagent-subprocess Shared machinery for **out-of-process subagent backends** — providers that spawn an external agent as a child process, such as the [ACP backend](../subagent-acp/README.md). A pure library (no provider, no registration, no Config): what every spawn-a-CLI-child backend needs to keep the parent deployment's credentials out of the child, tear the child down to quiescence, and isolate it from the host user's on-disk CLI state. Design rationale: [the Claude Code / Codex subagent backends RFC](../../../docs/rfc/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md). @@ -37,4 +37,4 @@ A per-run isolated config directory for an external CLI child (the target of `CL ## Testing -`tests/subagent-process.spec.ts`: the env scrub and config-dir helpers run against the real process env and real filesystem (the rm-failure path injects its rejection at the fs boundary — a real recursive-rm failure is not portably provokable, and root ignores permission bits); the exit waits and the dispose ladder run against a scriptable fake child, driving each escalation tier deterministically. The [ACP backend suite](../subagent-acp/README.md) exercises the same ladder against real subprocesses (EOF-cooperative, EOF-ignoring, and SIGTERM-trapping children) end to end. +`tests/subagent-subprocess.spec.ts`: the env scrub and config-dir helpers run against the real process env and real filesystem (the rm-failure path injects its rejection at the fs boundary — a real recursive-rm failure is not portably provokable, and root ignores permission bits); the exit waits and the dispose ladder run against a scriptable fake child, driving each escalation tier deterministically. The [ACP backend suite](../subagent-acp/README.md) exercises the same ladder against real subprocesses (EOF-cooperative, EOF-ignoring, and SIGTERM-trapping children) end to end. diff --git a/packages/subagent/subagent-process/package.json b/packages/subagent/subagent-subprocess/package.json similarity index 93% rename from packages/subagent/subagent-process/package.json rename to packages/subagent/subagent-subprocess/package.json index 218276be44..68f525dd8e 100644 --- a/packages/subagent/subagent-process/package.json +++ b/packages/subagent/subagent-subprocess/package.json @@ -1,5 +1,5 @@ { - "name": "@deepseek-ai/dsh-subagent-process", + "name": "@deepseek-ai/dsh-subagent-subprocess", "description": "Shared out-of-process subagent machinery: credential env scrub, spawn-failure capture, child-exit waits, the EOF-to-SIGTERM-to-SIGKILL dispose ladder, and isolated config dirs (pure lib; registers nothing)", "version": "0.0.1", "private": true, diff --git a/packages/subagent/subagent-process/src/index.ts b/packages/subagent/subagent-subprocess/src/index.ts similarity index 99% rename from packages/subagent/subagent-process/src/index.ts rename to packages/subagent/subagent-subprocess/src/index.ts index 219ed6b083..35d7383456 100644 --- a/packages/subagent/subagent-process/src/index.ts +++ b/packages/subagent/subagent-subprocess/src/index.ts @@ -15,7 +15,7 @@ * periods, a pinned config dir — is a PARAMETER here: defaults belong in each * consuming plugin's Config, per the no-hardcoded-tunables rule. * - * @module @deepseek-ai/dsh-subagent-process + * @module @deepseek-ai/dsh-subagent-subprocess */ import type { ChildProcess } from 'node:child_process' diff --git a/packages/subagent/subagent-process/tests/subagent-process.spec.ts b/packages/subagent/subagent-subprocess/tests/subagent-subprocess.spec.ts similarity index 94% rename from packages/subagent/subagent-process/tests/subagent-process.spec.ts rename to packages/subagent/subagent-subprocess/tests/subagent-subprocess.spec.ts index d23f075284..2766ed0a41 100644 --- a/packages/subagent/subagent-process/tests/subagent-process.spec.ts +++ b/packages/subagent/subagent-subprocess/tests/subagent-subprocess.spec.ts @@ -256,9 +256,9 @@ describe('disposeChildProcess', () => { describe('createIsolatedConfigDir', () => { it('creates a fresh private mkdtemp dir under the OS temp root', async () => { - const dir = await createIsolatedConfigDir('dsh-subagent-process-test-') + const dir = await createIsolatedConfigDir('dsh-subagent-subprocess-test-') try { - expect(dir.path.startsWith(join(tmpdir(), 'dsh-subagent-process-test-'))).toBe(true) + expect(dir.path.startsWith(join(tmpdir(), 'dsh-subagent-subprocess-test-'))).toBe(true) const st = await stat(dir.path) expect(st.isDirectory()).toBe(true) // Private (0700) per the defensive-patterns temp-dir rule. @@ -269,8 +269,8 @@ describe('createIsolatedConfigDir', () => { }) it('creates a distinct dir per call (per-run isolation)', async () => { - const a = await createIsolatedConfigDir('dsh-subagent-process-test-') - const b = await createIsolatedConfigDir('dsh-subagent-process-test-') + const a = await createIsolatedConfigDir('dsh-subagent-subprocess-test-') + const b = await createIsolatedConfigDir('dsh-subagent-subprocess-test-') try { expect(a.path).not.toBe(b.path) } finally { @@ -280,7 +280,7 @@ describe('createIsolatedConfigDir', () => { }) it('remove() deletes a fresh dir recursively and is idempotent', async () => { - const dir = await createIsolatedConfigDir('dsh-subagent-process-test-') + const dir = await createIsolatedConfigDir('dsh-subagent-subprocess-test-') await writeFile(join(dir.path, 'settings.json'), '{}') await dir.remove() expect(existsSync(dir.path)).toBe(false) @@ -289,7 +289,7 @@ describe('createIsolatedConfigDir', () => { }) it('returns a pinned dir verbatim and NEVER removes it', async () => { - const pinned = await mkdtemp(join(tmpdir(), 'dsh-subagent-process-pinned-')) + const pinned = await mkdtemp(join(tmpdir(), 'dsh-subagent-subprocess-pinned-')) try { const dir = await createIsolatedConfigDir('ignored-prefix-', pinned) expect(dir.path).toBe(pinned) @@ -302,7 +302,7 @@ describe('createIsolatedConfigDir', () => { }) it('does not create a missing pinned path (the deployment owns its lifecycle)', async () => { - const missing = join(tmpdir(), `dsh-subagent-process-missing-${process.pid}`) + const missing = join(tmpdir(), `dsh-subagent-subprocess-missing-${process.pid}`) const dir = await createIsolatedConfigDir('ignored-prefix-', missing) expect(dir.path).toBe(missing) expect(existsSync(missing)).toBe(false) @@ -311,7 +311,7 @@ describe('createIsolatedConfigDir', () => { }) it('remove() is best-effort: an rm rejection resolves instead of rejecting', async () => { - const dir = await createIsolatedConfigDir('dsh-subagent-process-locked-') + const dir = await createIsolatedConfigDir('dsh-subagent-subprocess-locked-') try { // The swallow contract is error-kind agnostic; EACCES stands in for the // family (EBUSY, a vanished mount, …) that best-effort must absorb. diff --git a/packages/subagent/subagent-process/tsconfig.json b/packages/subagent/subagent-subprocess/tsconfig.json similarity index 100% rename from packages/subagent/subagent-process/tsconfig.json rename to packages/subagent/subagent-subprocess/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 248405f643..023588835a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -635,9 +635,9 @@ importers: '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent - '@deepseek-ai/dsh-subagent-process': + '@deepseek-ai/dsh-subagent-subprocess': specifier: workspace:^ - version: link:../subagent-process + version: link:../subagent-subprocess cordis: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) @@ -715,12 +715,6 @@ importers: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) - packages/subagent/subagent-process: - devDependencies: - cordis: - specifier: ^4.0.0-rc.6 - version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) - packages/subagent/subagent-spawn: dependencies: schemastery: @@ -773,6 +767,12 @@ importers: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/subagent/subagent-subprocess: + devDependencies: + cordis: + specifier: ^4.0.0-rc.6 + version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/subagent/tool-subagent: dependencies: schemastery: diff --git a/tsconfig.build.json b/tsconfig.build.json index c5318e83fe..7fc770fac7 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -51,7 +51,7 @@ { "path": "./packages/support/subagent-mock" }, { "path": "./packages/subagent/tool-subagent" }, { "path": "./packages/subagent/subagent-inprocess" }, - { "path": "./packages/subagent/subagent-process" }, + { "path": "./packages/subagent/subagent-subprocess" }, { "path": "./packages/subagent/subagent-spawn" }, { "path": "./packages/subagent/subagent-fork" }, { "path": "./packages/subagent/subagent-acp" }, diff --git a/tsconfig.json b/tsconfig.json index 8882714d50..380d5f72f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -62,7 +62,7 @@ { "path": "./packages/support/subagent-mock" }, { "path": "./packages/subagent/tool-subagent" }, { "path": "./packages/subagent/subagent-inprocess" }, - { "path": "./packages/subagent/subagent-process" }, + { "path": "./packages/subagent/subagent-subprocess" }, { "path": "./packages/subagent/subagent-spawn" }, { "path": "./packages/subagent/subagent-fork" }, { "path": "./packages/subagent/subagent-acp" },