From fc566119a728c17066cb458a61da1c0e1bb010c7 Mon Sep 17 00:00:00 2001
From: Tianyi Cui <53024+tianyicui@users.noreply.github.com>
Date: Sun, 26 Jul 2026 12:43:14 +0800
Subject: [PATCH] refactor(subprocess): rename the process seam to subprocess
and address review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Review feedback (tianyicui): 'process' is a poor service name. The family is
now packages/subprocess/ — @deepseek-ai/dsh-subprocess (ctx.subprocess,
abstract SubprocessService, Subprocess* vocabulary) and
@deepseek-ai/dsh-subprocess-local (LocalSubprocessService) — renamed
throughout code, compositions, docs (en+zh, pairs re-recorded), catalogs,
and gates. 'subprocess' is the precise term for managed OS children (the
Python-stdlib sense), avoids colliding with Node's global process object,
and reads as one system beside dsh-subagent-subprocess.
ds-review-bot findings addressed:
- kill() on a settled handle is now a no-op (no signal to a possibly-reused
pgid, no referenced grace timer delaying exit); pinned by a spy test.
- The moved DshEnvironmentKey/DshEnvironment/CollectedOutput types get
drift-checked type-equiv blocks on the new subprocess.md page, restoring
their manifest registration.
- subprocess.md is registered in the core.md sub-page index (en+zh).
---
...6-07-06-timeout-deadline-library.i18n.yaml | 4 +-
.../2026-07-06-timeout-deadline-library.md | 2 +-
.../2026-07-06-timeout-deadline-library.zh.md | 2 +-
.../2026-07-26-process-manager-seam.md | 38 ------
...l => 2026-07-26-subprocess-seam.i18n.yaml} | 4 +-
.../2026-07-26-subprocess-seam.md | 38 ++++++
...zh.md => 2026-07-26-subprocess-seam.zh.md} | 20 +--
apps/cli/cordis.yml | 4 +-
apps/cli/package.json | 2 +-
docs/architecture.i18n.yaml | 4 +-
docs/architecture.md | 4 +-
docs/architecture.zh.md | 4 +-
docs/capability-seams.md | 12 +-
docs/config-catalog.md | 8 +-
docs/cordis-catalog/services.md | 54 ++++----
docs/core-data-structures/bash.i18n.yaml | 4 +-
docs/core-data-structures/bash.md | 10 +-
docs/core-data-structures/bash.zh.md | 10 +-
docs/core-data-structures/core.i18n.yaml | 4 +-
docs/core-data-structures/core.md | 1 +
docs/core-data-structures/core.zh.md | 1 +
.../{process.md => subprocess.md} | 62 ++++++---
docs/module-graph.md | 26 ++--
examples/acp-agent/composition.md | 6 +-
examples/acp-agent/cordis.yml | 4 +-
examples/cordis-agent/composition.md | 6 +-
examples/cordis-agent/cordis.yml | 4 +-
examples/headless-agent/composition.md | 6 +-
examples/headless-agent/cordis.yml | 4 +-
.../semantic-checkpoint.cordis.snapshot.yml | 4 +-
.../headless-agent/tests/code-mode.e2e.ts | 6 +-
.../tests/fixtures/goal-domain/cordis.yml | 4 +-
.../tests/fixtures/time-context.cordis.yml | 4 +-
examples/headless-agent/tests/harness.ts | 4 +-
examples/jsonrpc-agent/cordis.yml | 4 +-
examples/package.json | 2 +-
examples/tui-agent/composition.md | 6 +-
examples/tui-agent/cordis.yml | 4 +-
.../tests/fixtures/tui-scripted.cordis.yml | 4 +-
examples/tui-agent/tests/tui.snapshot.ts | 4 +-
packages/README.md | 2 +-
packages/bash/README.md | 4 +-
packages/bash/bash-local/README.md | 14 +-
packages/bash/bash-local/package.json | 6 +-
packages/bash/bash-local/src/index.ts | 24 ++--
.../bash/bash-local/tests/executor.spec.ts | 22 ++--
packages/bash/bash-local/tsconfig.json | 2 +-
packages/bash/bash-sandbox/package.json | 2 +-
packages/bash/bash-sandbox/src/index.ts | 4 +-
packages/bash/bash-sandbox/tests/bwrap.e2e.ts | 4 +-
.../bash/bash-sandbox/tests/landlock.e2e.ts | 4 +-
.../bash/bash-sandbox/tests/sandbox.spec.ts | 6 +-
.../bash/bash-sandbox/tests/seatbelt.e2e.ts | 4 +-
packages/bash/bash/package.json | 4 +-
packages/bash/bash/src/index.ts | 4 +-
packages/bash/bash/src/types.ts | 10 +-
packages/bash/bash/tsconfig.json | 2 +-
packages/bash/tool-bash/package.json | 2 +-
.../bash/tool-bash/tests/integration.spec.ts | 4 +-
packages/bash/tool-bash/tests/tools.spec.ts | 22 ++--
.../cordis/tool-cordis/src/api-catalog.ts | 52 ++------
.../examples/acp-demo/tests/built-bin.e2e.ts | 4 +-
.../examples/acp-demo/tests/load-path.e2e.ts | 4 +-
.../examples/agent-spine-demo/package.json | 2 +-
.../tests/multi-project-sandbox.e2e.ts | 4 +-
.../examples/cli-demo/tests/built-bin.e2e.ts | 4 +-
packages/fs/tool-fs-search/package.json | 2 +-
.../tool-fs-search/tests/integration.spec.ts | 4 +-
packages/hooks/hooks-claude/package.json | 2 +-
.../hooks/hooks-claude/tests/bridge.spec.ts | 8 +-
.../hooks-claude/tests/coverage-cases.ts | 10 +-
packages/hooks/hooks-codex/package.json | 2 +-
.../hooks/hooks-codex/tests/bridge.spec.ts | 8 +-
.../hooks/hooks-codex/tests/coverage-cases.ts | 8 +-
packages/process/README.md | 10 --
.../sdk/helper/src/features/builtin/index.ts | 2 +-
.../subagent-inprocess/src/invariant.ts | 2 +-
packages/subagent/subagent-spawn/package.json | 2 +-
.../subagent/subagent-spawn/tests/harness.ts | 4 +-
.../subagent-subprocess/src/invariant.ts | 2 +-
packages/subprocess/README.md | 10 ++
.../subprocess-local}/README.md | 8 +-
.../subprocess-local}/package.json | 8 +-
.../subprocess-local}/src/index.ts | 20 +--
.../subprocess-local}/src/invariant.ts | 8 +-
.../subprocess-local}/src/spawn.ts | 24 ++--
.../subprocess-local/tests/local.spec.ts} | 36 +++---
.../subprocess-local}/tests/spawn.spec.ts | 27 +++-
.../subprocess-local}/tsconfig.json | 2 +-
.../subprocess}/README.md | 10 +-
.../subprocess}/package.json | 4 +-
.../subprocess}/src/index.ts | 34 ++---
.../subprocess}/src/invariant.ts | 8 +-
.../subprocess}/src/types.ts | 28 ++--
.../subprocess}/tests/service.spec.ts | 26 ++--
.../subprocess}/tsconfig.json | 0
pnpm-lock.yaml | 122 +++++++++---------
python/sdk-runtime/package.json | 4 +-
.../runtime/cordis.yml | 4 +-
python/sdk/tests/test_bundled_runtime.py | 4 +-
scripts/gen-cordis-catalog.ts | 12 +-
scripts/gen-doc-graphs.ts | 4 +-
scripts/gen-tool-catalog.ts | 4 +-
scripts/type-equiv.manifest.json | 45 ++++---
.../verify-package-readme-model-experience.ts | 4 +-
tsconfig.base.json | 4 +-
tsconfig.host.json | 4 +-
vitest.config.ts | 4 +-
108 files changed, 587 insertions(+), 557 deletions(-)
delete mode 100644 .agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md
rename .agents/notes/implemented/architecture/{2026-07-26-process-manager-seam.i18n.yaml => 2026-07-26-subprocess-seam.i18n.yaml} (65%)
create mode 100644 .agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md
rename .agents/notes/implemented/architecture/{2026-07-26-process-manager-seam.zh.md => 2026-07-26-subprocess-seam.zh.md} (50%)
rename docs/core-data-structures/{process.md => subprocess.md} (59%)
delete mode 100644 packages/process/README.md
create mode 100644 packages/subprocess/README.md
rename packages/{process/process-local => subprocess/subprocess-local}/README.md (78%)
rename packages/{process/process-local => subprocess/subprocess-local}/package.json (83%)
rename packages/{process/process-local => subprocess/subprocess-local}/src/index.ts (69%)
rename packages/{process/process-local => subprocess/subprocess-local}/src/invariant.ts (77%)
rename packages/{process/process-local => subprocess/subprocess-local}/src/spawn.ts (92%)
rename packages/{process/process-local/tests/manager.spec.ts => subprocess/subprocess-local/tests/local.spec.ts} (57%)
rename packages/{process/process-local => subprocess/subprocess-local}/tests/spawn.spec.ts (95%)
rename packages/{process/process-local => subprocess/subprocess-local}/tsconfig.json (92%)
rename packages/{process/process => subprocess/subprocess}/README.md (61%)
rename packages/{process/process => subprocess/subprocess}/package.json (77%)
rename packages/{process/process => subprocess/subprocess}/src/index.ts (65%)
rename packages/{process/process => subprocess/subprocess}/src/invariant.ts (73%)
rename packages/{process/process => subprocess/subprocess}/src/types.ts (87%)
rename packages/{process/process => subprocess/subprocess}/tests/service.spec.ts (60%)
rename packages/{process/process => subprocess/subprocess}/tsconfig.json (100%)
diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml
index b213b86ba1..471cf9f92d 100644
--- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.i18n.yaml
@@ -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
-2026-07-06-timeout-deadline-library.md: 8e739b111644aaa00e2cba9f6de803327de90e26
-2026-07-06-timeout-deadline-library.zh.md: 60d6c60b396b91b0504c7e36d1465787e8b6ae66
+2026-07-06-timeout-deadline-library.md: 63463a76a65743436d4e78479800c19e257a42de
+2026-07-06-timeout-deadline-library.zh.md: c3d3cdf1c63813fc24c10727e42d326142f3f4de
diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md
index 8e739b1116..63463a76a6 100644
--- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md
+++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md
@@ -8,7 +8,7 @@ English | [中文](2026-07-06-timeout-deadline-library.zh.md)
Timeout handling was drifting apart across the tool-bearing capabilities, and the divergence was not superficial — it was the same logic re-implemented three ways, each with its own subtle correctness burden.
-- **bash** (then in the bash-local implementation's `run.ts`) had a full, correct timeout inside the process plumbing: a config-clamped `timeoutMs`, two independent triggers — a `killTimer` for the timeout and an `onAbort` listener for upstream cancellation — each calling one `kill()` closure that escalates SIGTERM→grace→SIGKILL on the process group, and two orthogonal outcome booleans (`timedOut`, `aborted`) latched independently. After this consolidation, the plumbing — today [packages/process/process-local/src/spawn.ts](../../../../packages/process/process-local/src/spawn.ts) — only reacts to aborts; [packages/bash/bash-local/src/index.ts](../../../../packages/bash/bash-local/src/index.ts) owns the fused deadline and the `timedOut`/`aborted` classification.
+- **bash** (then in the bash-local implementation's `run.ts`) had a full, correct timeout inside the process plumbing: a config-clamped `timeoutMs`, two independent triggers — a `killTimer` for the timeout and an `onAbort` listener for upstream cancellation — each calling one `kill()` closure that escalates SIGTERM→grace→SIGKILL on the process group, and two orthogonal outcome booleans (`timedOut`, `aborted`) latched independently. After this consolidation, the plumbing — today [packages/subprocess/subprocess-local/src/spawn.ts](../../../../packages/subprocess/subprocess-local/src/spawn.ts) — only reacts to aborts; [packages/bash/bash-local/src/index.ts](../../../../packages/bash/bash-local/src/index.ts) owns the fused deadline and the `timedOut`/`aborted` classification.
- **web_fetch** ([packages/web/web-fetch-local/src/provider.ts](../../../../packages/web/web-fetch-local/src/provider.ts)) had a correct but *hand-rolled* timeout: it constructed an `AbortController`, wired `setTimeout(() => controller.abort(new WebError(…, 'WEB_FETCH_TIMEOUT')))`, manually added and removed the upstream-signal listener, cleared the timer in a `finally`, and recovered the timeout reason from `signal.reason` in a `translateAbortOrNetwork` helper because the reader surfaces a bare `AbortError`.
- **web_search** ([packages/web/tool-web/src/search.ts](../../../../packages/web/tool-web/src/search.ts)) had **no timeout at all**: `WebSearchRequest` ([packages/web/web/src/types.ts](../../../../packages/web/web/src/types.ts)) carries no `timeoutMs` field, and each provider's `search()` only forwards `exec.signal`. (web_search stays untimed here — see Consequences.)
diff --git a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md
index 60d6c60b39..c3d3cdf1c6 100644
--- a/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md
+++ b/.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.zh.md
@@ -8,7 +8,7 @@ Status: implemented
超时处理在各个承载工具的能力之间逐渐分化,而且这种分化并非表面的:同一套逻辑被以三种方式重新实现,各自带有微妙的正确性负担。
-- **bash**(当时位于 bash-local 实现的 `run.ts`)在进程管道内部有一套完整、正确的超时实现:一个经配置钳位的 `timeoutMs`,两个独立触发器(用于超时的 `killTimer` 和用于上游取消的 `onAbort` 监听器),各自调用同一个 `kill()` 闭包对进程组执行 SIGTERM→宽限期→SIGKILL 升级,以及两个正交的结果布尔值(`timedOut`、`aborted`)独立锁存。经此次整合之后,这套管道——今天位于 [packages/process/process-local/src/spawn.ts](../../../../packages/process/process-local/src/spawn.ts)——只响应中止;[packages/bash/bash-local/src/index.ts](../../../../packages/bash/bash-local/src/index.ts) 拥有融合的 deadline 以及 `timedOut`/`aborted` 分类。
+- **bash**(当时位于 bash-local 实现的 `run.ts`)在进程管道内部有一套完整、正确的超时实现:一个经配置钳位的 `timeoutMs`,两个独立触发器(用于超时的 `killTimer` 和用于上游取消的 `onAbort` 监听器),各自调用同一个 `kill()` 闭包对进程组执行 SIGTERM→宽限期→SIGKILL 升级,以及两个正交的结果布尔值(`timedOut`、`aborted`)独立锁存。经此次整合之后,这套管道——今天位于 [packages/subprocess/subprocess-local/src/spawn.ts](../../../../packages/subprocess/subprocess-local/src/spawn.ts)——只响应中止;[packages/bash/bash-local/src/index.ts](../../../../packages/bash/bash-local/src/index.ts) 拥有融合的 deadline 以及 `timedOut`/`aborted` 分类。
- **web_fetch**([packages/web/web-fetch-local/src/provider.ts](../../../../packages/web/web-fetch-local/src/provider.ts))有一套正确但*手写*的超时:构造一个 `AbortController`,连接 `setTimeout(() => controller.abort(new WebError(…, 'WEB_FETCH_TIMEOUT')))`,手动添加和移除上游信号监听器,在 `finally` 中清除定时器,并在 `translateAbortOrNetwork` 辅助函数中从 `signal.reason` 恢复超时原因(因为 reader 只抛出裸 `AbortError`)。
- **web_search**([packages/web/tool-web/src/search.ts](../../../../packages/web/tool-web/src/search.ts))**完全没有超时**:`WebSearchRequest`([packages/web/web/src/types.ts](../../../../packages/web/web/src/types.ts))不携带 `timeoutMs` 字段,各提供方的 `search()` 只转发 `exec.signal`。(web_search 在本次设计中保持无超时——见「后果」。)
diff --git a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md b/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md
deleted file mode 100644
index 2157259517..0000000000
--- a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Agent Note: The process manager is its own seam under the bash executors (`dsh-process` / `dsh-process-local`)
-
-Status: implemented
-
-English | [中文](2026-07-26-process-manager-seam.zh.md)
-
-## Problem
-
-`dsh-bash-local` bundled two capabilities that change for different reasons: *running a bash command* (command defaulting, timeout classification, model-friendly terminal environment, the stdout/stderr merge the bash tool renders) and *running and managing a child process* (detached process groups, bounded tail-keep output with spill files, the credential scrub and `DSH_*` merge order, SIGTERM→grace→SIGKILL escalation, kill-and-join disposal). The process half — `run.ts`, roughly half the package — had no seam of its own: a future non-shell runner (a direct-argv executor, a worker supervisor) would have to re-implement or reach into bash internals, and the shared `DSH_*`/`CollectedOutput` vocabulary lived in a package whose name promises shell semantics. The bundling also tied background-process lifetime to the executor's fiber: reloading the bash executor killed every live background process, unlike the sibling [task registry](2026-07-26-task-registry-seam.md), whose registrations deliberately outlive producer fibers.
-
-## Decision
-
-A new `process/` capability family owns "run and manage a process"; the bash family keeps "run a bash command" and consumes it:
-
-- **`@deepseek-ai/dsh-process` (interface)** — the abstract `ProcessManager` owning `ctx.processes` with one method, `spawn(spec): ProcessHandle`, and the shared vocabulary: the fully-explicit `ProcessSpawnSpec` (argv, cwd, per-stream caps, spill cap, grace — no defaults; deployment-varying knobs stay with the calling seam's config, per the `dsh-bash` request/spec template and the no-hidden-defaults rule), `ProcessHandle` with non-consuming offset-based readers, `ProcessOutcome` with deliberately no timeout/cancel classification, and the `DSH_ENV_PREFIX`/`DshEnvironment`/`CollectedOutput` types. `argv` is never shell-interpreted.
-- **`@deepseek-ai/dsh-process-local` (implementation)** — `LocalProcessManager` over the former `run.ts` plumbing (`spawn.ts`): detached groups, tail-keep truncation with private bounded spill files, credential scrub with the two-channel `DSH_*` merge, group kill escalation, and disposal that kills and joins every still-running managed process. It has no config; every limit arrives on the spec. The terminal `ENV_OVERRIDES` (`TERM=dumb` etc.) did NOT move — that is bash-tool presentation policy and stays in `dsh-bash-local`, merged through the ordinary env channel.
-- **`dsh-bash-local` (consumer)** — `inject: ['processes']`; maps each resolved `BashExecSpec` onto a `ProcessSpawnSpec` (`['bash', '-c', command]`), keeps its config, `resolve()` defaulting, fused-deadline `timedOut`/`aborted` classification, the `[stderr]`-marked background read merge with its consuming cursor, and the `onProcessDone` subclass hook. `dsh-bash-sandbox` is unchanged apart from redeclaring the inherited inject; it still wraps at the command-string level and re-enters the inherited spawn path.
-- **`dsh-bash` (seam)** — re-exports the moved vocabulary from `dsh-process`, so no bash consumer changes an import; `BashExecRequest`/`BashExecSpec`/`BashProcess` and the sandbox facts remain bash-owned.
-
-Every composition that loads a bash executor now also loads `@deepseek-ai/dsh-process-local` (CLI, examples, python bundled runtime, create-sdk's bash feature resources, inline test configs).
-
-Background-process lifetime moved from the executor to the manager: the executor no longer retains a live-process set, so an executor reload leaves background work running and readable, and composition teardown (the manager's disposal) remains the kill-and-join boundary. One behavioral seam shifted with it: a background spawn failure can no longer be buffered as fake stderr inside the plumbing (the manager rejects `done` and buffers nothing for a process that never ran), so the executor injects the `spawn failed: …` note into exactly one `readOutput()` delta.
-
-## Alternatives considered
-
-**Leave the process plumbing inside `dsh-bash-local` (status quo).** Rejected for the same reason the [task registry split](2026-07-26-task-registry-seam.md) landed: the boundary is stable and already documented in-code (`run.ts`'s module doc said "this layer reacts to an abort signal; the executor owns deadlines and classifies causes"), and keeping it private makes every future non-shell runner either fork the mechanics or depend on a bash-named package for non-bash work. The user-visible driver for this stack was exactly this split.
-
-**Migrate the repo's other spawn sites (lsp-local, pty-local, subagent-subprocess, sdk package-manager, test-support launchers) onto `ctx.processes` in the same change.** Rejected as scope creep with real design risk: those sites have materially different stream and lifecycle needs — node-pty ownership (pty), LSP framing over long-lived stdio with tree-kill fallbacks (lsp), stdin-EOF-first disposal ladders and no output buffering (subagent transports) — and forcing them under a handle shaped for bounded batch output would either bloat the seam or misfit the consumers. The seam ships proven against its one real consumer family, per the shape-interfaces-around-current-consumers rule; the others are named as deferred work in the seam README.
-
-**Put `run_in_background`/task semantics into the process seam instead.** Rejected: that boundary already exists — `ctx.tasks` owns ids, ownership, and notices, and the bash tool adapts a `BashProcess` into task hooks. The process seam sits *below* the bash executor, not beside the task registry.
-
-**Move `ENV_OVERRIDES` (TERM=dumb, PAGER=cat …) into the manager.** Rejected: a generic process manager must not impose terminal presentation policy on non-terminal consumers; the scrub and `DSH_*` channel rules are security/identity invariants and stay, but terminal friendliness is the bash tool's choice, expressed through the ordinary env channel where an explicit caller entry still wins.
-
-## Consequences
-
-Bought: "run and manage a process" is a swappable capability with the standard three-package shape (consumer count starts at two: `bash-local`, `bash-sandbox`); a containerized or remote process backend slots in without touching bash semantics; the shared `DSH_*`/output vocabulary has a non-shell home; and background processes survive executor reloads, matching the task registry's lifetime model. The spawn plumbing suite moved wholesale to `dsh-process-local` (argv-based, plus argv-validation and manager lifecycle/disposal suites); the executor suite now pins the bash-owned layers (classification, merge, spawn-failure note, manager-owned lifetime) against the real manager.
-
-Cost: one more package pair and one more composition row everywhere a bash executor loads — a boot that loads an executor without the manager leaves `ctx.bash` pending on `ctx.processes` (standard missing-service behavior). The moved-vocabulary re-exports keep `dsh-bash` imports working but mean two packages now name the same types; the process seam is the owner and the bash seam documents the re-export. The spawn-failure note became single-delivery through the read path where the old plumbing retained it in the stderr buffer for repeated `readFrom(0)` reads — acceptable because the bash background read path was already a consuming cursor, and the note reaches the one reader that exists.
diff --git a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.i18n.yaml b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.i18n.yaml
similarity index 65%
rename from .agents/notes/implemented/architecture/2026-07-26-process-manager-seam.i18n.yaml
rename to .agents/notes/implemented/architecture/2026-07-26-subprocess-seam.i18n.yaml
index fc1952eaa6..a0ea989893 100644
--- a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.i18n.yaml
+++ b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.i18n.yaml
@@ -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
-2026-07-26-process-manager-seam.md: 215725951792d23053c93da19c619c08393af1b9
-2026-07-26-process-manager-seam.zh.md: 03062f33d3d5fbafa1ce0b9bc3e723d10572cfd4
+2026-07-26-subprocess-seam.md: cd7b18c209af0830e339abfe01aa91dc493e15da
+2026-07-26-subprocess-seam.zh.md: 685d9797a7456b4edf95853b253dc97f3426c902
diff --git a/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md
new file mode 100644
index 0000000000..cd7b18c209
--- /dev/null
+++ b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md
@@ -0,0 +1,38 @@
+# Agent Note: The subprocess service is its own seam under the bash executors (`dsh-subprocess` / `dsh-subprocess-local`)
+
+Status: implemented
+
+English | [中文](2026-07-26-subprocess-seam.zh.md)
+
+## Problem
+
+`dsh-bash-local` bundled two capabilities that change for different reasons: *running a bash command* (command defaulting, timeout classification, model-friendly terminal environment, the stdout/stderr merge the bash tool renders) and *running and managing a child process* (detached process groups, bounded tail-keep output with spill files, the credential scrub and `DSH_*` merge order, SIGTERM→grace→SIGKILL escalation, kill-and-join disposal). The process half — `run.ts`, roughly half the package — had no seam of its own: a future non-shell runner (a direct-argv executor, a worker supervisor) would have to re-implement or reach into bash internals, and the shared `DSH_*`/`CollectedOutput` vocabulary lived in a package whose name promises shell semantics. The bundling also tied background-process lifetime to the executor's fiber: reloading the bash executor killed every live background process, unlike the sibling [task registry](2026-07-26-task-registry-seam.md), whose registrations deliberately outlive producer fibers.
+
+## Decision
+
+A new `process/` capability family owns "run and manage a process"; the bash family keeps "run a bash command" and consumes it:
+
+- **`@deepseek-ai/dsh-subprocess` (interface)** — the abstract `SubprocessService` owning `ctx.subprocess` with one method, `spawn(spec): SubprocessHandle`, and the shared vocabulary: the fully-explicit `SubprocessSpawnSpec` (argv, cwd, per-stream caps, spill cap, grace — no defaults; deployment-varying knobs stay with the calling seam's config, per the `dsh-bash` request/spec template and the no-hidden-defaults rule), `SubprocessHandle` with non-consuming offset-based readers, `SubprocessOutcome` with deliberately no timeout/cancel classification, and the `DSH_ENV_PREFIX`/`DshEnvironment`/`CollectedOutput` types. `argv` is never shell-interpreted.
+- **`@deepseek-ai/dsh-subprocess-local` (implementation)** — `LocalSubprocessService` over the former `run.ts` plumbing (`spawn.ts`): detached groups, tail-keep truncation with private bounded spill files, credential scrub with the two-channel `DSH_*` merge, group kill escalation, and disposal that kills and joins every still-running managed process. It has no config; every limit arrives on the spec. The terminal `ENV_OVERRIDES` (`TERM=dumb` etc.) did NOT move — that is bash-tool presentation policy and stays in `dsh-bash-local`, merged through the ordinary env channel.
+- **`dsh-bash-local` (consumer)** — `inject: ['subprocess']`; maps each resolved `BashExecSpec` onto a `SubprocessSpawnSpec` (`['bash', '-c', command]`), keeps its config, `resolve()` defaulting, fused-deadline `timedOut`/`aborted` classification, the `[stderr]`-marked background read merge with its consuming cursor, and the `onProcessDone` subclass hook. `dsh-bash-sandbox` is unchanged apart from redeclaring the inherited inject; it still wraps at the command-string level and re-enters the inherited spawn path.
+- **`dsh-bash` (seam)** — re-exports the moved vocabulary from `dsh-subprocess`, so no bash consumer changes an import; `BashExecRequest`/`BashExecSpec`/`BashProcess` and the sandbox facts remain bash-owned.
+
+Every composition that loads a bash executor now also loads `@deepseek-ai/dsh-subprocess-local` (CLI, examples, python bundled runtime, create-sdk's bash feature resources, inline test configs).
+
+Background-process lifetime moved from the executor to the subprocess service: the executor no longer retains a live-process set, so an executor reload leaves background work running and readable, and composition teardown (the service's disposal) remains the kill-and-join boundary. One behavioral seam shifted with it: a background spawn failure can no longer be buffered as fake stderr inside the plumbing (the service rejects `done` and buffers nothing for a process that never ran), so the executor injects the `spawn failed: …` note into exactly one `readOutput()` delta.
+
+## Alternatives considered
+
+**Leave the process plumbing inside `dsh-bash-local` (status quo).** Rejected for the same reason the [task registry split](2026-07-26-task-registry-seam.md) landed: the boundary is stable and already documented in-code (`run.ts`'s module doc said "this layer reacts to an abort signal; the executor owns deadlines and classifies causes"), and keeping it private makes every future non-shell runner either fork the mechanics or depend on a bash-named package for non-bash work. The user-visible driver for this stack was exactly this split.
+
+**Migrate the repo's other spawn sites (lsp-local, pty-local, subagent-subprocess, sdk package-manager, test-support launchers) onto `ctx.subprocess` in the same change.** Rejected as scope creep with real design risk: those sites have materially different stream and lifecycle needs — node-pty ownership (pty), LSP framing over long-lived stdio with tree-kill fallbacks (lsp), stdin-EOF-first disposal ladders and no output buffering (subagent transports) — and forcing them under a handle shaped for bounded batch output would either bloat the seam or misfit the consumers. The seam ships proven against its one real consumer family, per the shape-interfaces-around-current-consumers rule; the others are named as deferred work in the seam README.
+
+**Put `run_in_background`/task semantics into the process seam instead.** Rejected: that boundary already exists — `ctx.tasks` owns ids, ownership, and notices, and the bash tool adapts a `BashProcess` into task hooks. The process seam sits *below* the bash executor, not beside the task registry.
+
+**Move `ENV_OVERRIDES` (TERM=dumb, PAGER=cat …) into the subprocess service.** Rejected: a generic subprocess service must not impose terminal presentation policy on non-terminal consumers; the scrub and `DSH_*` channel rules are security/identity invariants and stay, but terminal friendliness is the bash tool's choice, expressed through the ordinary env channel where an explicit caller entry still wins.
+
+## Consequences
+
+Bought: "run and manage a process" is a swappable capability with the standard three-package shape (consumer count starts at two: `bash-local`, `bash-sandbox`); a containerized or remote process backend slots in without touching bash semantics; the shared `DSH_*`/output vocabulary has a non-shell home; and background processes survive executor reloads, matching the task registry's lifetime model. The spawn plumbing suite moved wholesale to `dsh-subprocess-local` (argv-based, plus argv-validation and service lifecycle/disposal suites); the executor suite now pins the bash-owned layers (classification, merge, spawn-failure note, service-owned lifetime) against the real service.
+
+Cost: one more package pair and one more composition row everywhere a bash executor loads — a boot that loads an executor without the subprocess service leaves `ctx.bash` pending on `ctx.subprocess` (standard missing-service behavior). The moved-vocabulary re-exports keep `dsh-bash` imports working but mean two packages now name the same types; the subprocess seam is the owner and the bash seam documents the re-export. The spawn-failure note became single-delivery through the read path where the old plumbing retained it in the stderr buffer for repeated `readFrom(0)` reads — acceptable because the bash background read path was already a consuming cursor, and the note reaches the one reader that exists.
diff --git a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.zh.md b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.zh.md
similarity index 50%
rename from .agents/notes/implemented/architecture/2026-07-26-process-manager-seam.zh.md
rename to .agents/notes/implemented/architecture/2026-07-26-subprocess-seam.zh.md
index 03062f33d3..685d9797a7 100644
--- a/.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.zh.md
+++ b/.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.zh.md
@@ -1,8 +1,8 @@
-# Agent Note: 进程管理器是 bash 执行器之下的独立 seam(`dsh-process` / `dsh-process-local`)
+# Agent Note: 进程管理器是 bash 执行器之下的独立 seam(`dsh-subprocess` / `dsh-subprocess-local`)
Status: implemented
-[English](2026-07-26-process-manager-seam.md) | 中文
+[English](2026-07-26-subprocess-seam.md) | 中文
## 问题
@@ -12,12 +12,12 @@ Status: implemented
新的 `process/` 能力家族拥有「运行并管理一个进程」;bash 家族保留「运行一条 bash 命令」,并成为前者的消费方:
-- **`@deepseek-ai/dsh-process`(接口)**——拥有 `ctx.processes` 的抽象 `ProcessManager`(仅一个方法:`spawn(spec): ProcessHandle`),以及共享词汇:完全显式的 `ProcessSpawnSpec`(argv、cwd、按流划分的上限、spill 上限、宽限期,一律不设默认值;随部署变化的旋钮依照 `dsh-bash` 的 request/spec 模板与无隐藏默认值规则,留在调用方 seam 的配置里)、携带基于偏移量的非消费式读取器的 `ProcessHandle`、刻意不含超时/取消分类的 `ProcessOutcome`,以及 `DSH_ENV_PREFIX`/`DshEnvironment`/`CollectedOutput` 类型。`argv` 绝不经过 shell 解释。
-- **`@deepseek-ai/dsh-process-local`(实现)**——`LocalProcessManager`,构建在原 `run.ts` 管道(现为 `spawn.ts`)之上:detached 进程组、带私有有界 spill 文件的尾部保留截断、带双通道 `DSH_*` 合并的凭据清除、进程组 kill 升级,以及会终止每个仍在运行的受管进程并等待其退出的 dispose。该实现没有任何配置;每项限制都随 spec 到达。终端相关的 `ENV_OVERRIDES`(`TERM=dumb` 等)并未迁移:那是 bash 工具的呈现策略,留在 `dsh-bash-local` 里,经普通 env 通道合并。
-- **`dsh-bash-local`(消费方)**——`inject: ['processes']`;把每个解析后的 `BashExecSpec` 映射为一个 `ProcessSpawnSpec`(`['bash', '-c', command]`),并保留自身配置、`resolve()` 默认值补全、基于融合 deadline 的 `timedOut`/`aborted` 分类、带 `[stderr]` 标记的后台读取合并及其消费游标,以及 `onProcessDone` 子类钩子。`dsh-bash-sandbox` 除了重新声明继承来的 inject 之外没有变化;它仍在命令字符串层面做包装,并重新进入继承的 spawn 路径。
-- **`dsh-bash`(seam)**——把迁走的词汇从 `dsh-process` 重导出,因此没有任何 bash 消费方需要改动导入;`BashExecRequest`/`BashExecSpec`/`BashProcess` 与沙箱事实仍归 bash 所有。
+- **`@deepseek-ai/dsh-subprocess`(接口)**——拥有 `ctx.subprocess` 的抽象 `SubprocessService`(仅一个方法:`spawn(spec): SubprocessHandle`),以及共享词汇:完全显式的 `SubprocessSpawnSpec`(argv、cwd、按流划分的上限、spill 上限、宽限期,一律不设默认值;随部署变化的旋钮依照 `dsh-bash` 的 request/spec 模板与无隐藏默认值规则,留在调用方 seam 的配置里)、携带基于偏移量的非消费式读取器的 `SubprocessHandle`、刻意不含超时/取消分类的 `SubprocessOutcome`,以及 `DSH_ENV_PREFIX`/`DshEnvironment`/`CollectedOutput` 类型。`argv` 绝不经过 shell 解释。
+- **`@deepseek-ai/dsh-subprocess-local`(实现)**——`LocalSubprocessService`,构建在原 `run.ts` 管道(现为 `spawn.ts`)之上:detached 进程组、带私有有界 spill 文件的尾部保留截断、带双通道 `DSH_*` 合并的凭据清除、进程组 kill 升级,以及会终止每个仍在运行的受管进程并等待其退出的 dispose。该实现没有任何配置;每项限制都随 spec 到达。终端相关的 `ENV_OVERRIDES`(`TERM=dumb` 等)并未迁移:那是 bash 工具的呈现策略,留在 `dsh-bash-local` 里,经普通 env 通道合并。
+- **`dsh-bash-local`(消费方)**——`inject: ['subprocess']`;把每个解析后的 `BashExecSpec` 映射为一个 `SubprocessSpawnSpec`(`['bash', '-c', command]`),并保留自身配置、`resolve()` 默认值补全、基于融合 deadline 的 `timedOut`/`aborted` 分类、带 `[stderr]` 标记的后台读取合并及其消费游标,以及 `onProcessDone` 子类钩子。`dsh-bash-sandbox` 除了重新声明继承来的 inject 之外没有变化;它仍在命令字符串层面做包装,并重新进入继承的 spawn 路径。
+- **`dsh-bash`(seam)**——把迁走的词汇从 `dsh-subprocess` 重导出,因此没有任何 bash 消费方需要改动导入;`BashExecRequest`/`BashExecSpec`/`BashProcess` 与沙箱事实仍归 bash 所有。
-如今,每个加载 bash 执行器的组合都同时加载 `@deepseek-ai/dsh-process-local`:CLI(命令行界面)、各示例、Python 捆绑运行时、create-sdk 的 bash 功能资源,以及各内联测试配置。
+如今,每个加载 bash 执行器的组合都同时加载 `@deepseek-ai/dsh-subprocess-local`:CLI(命令行界面)、各示例、Python 捆绑运行时、create-sdk 的 bash 功能资源,以及各内联测试配置。
后台进程的存续期从执行器移到了管理器:执行器不再保有存活进程集合,于是重载执行器后,后台工作会继续运行且仍可读取,而组合拆除(管理器的 dispose)仍是先终止再等待退出的边界。一条行为 seam 随之挪动:后台 spawn 失败不再能在管道内部被缓冲成伪造的 stderr(对一个从未真正运行的进程,管理器会 reject `done`,且不缓冲任何内容),因此执行器把 `spawn failed: …` 提示注入恰好一个 `readOutput()` 增量。
@@ -25,7 +25,7 @@ Status: implemented
**把进程管道留在 `dsh-bash-local` 里(维持现状)。**否决的理由与[任务注册表拆分](2026-07-26-task-registry-seam.md)得以落地的理由相同:这条边界既稳定,也早已记录在代码里(`run.ts` 的模块文档曾写明「this layer reacts to an abort signal; the executor owns deadlines and classifies causes」),而若继续将它保持私有,未来每个非 shell 运行器就只能要么 fork 这套机制,要么为非 bash 工作去依赖一个以 bash 命名的包。这组堆叠变更对用户可见的动因正是这一拆分。
-**在同一变更中把仓库其余 spawn 调用点(lsp-local、pty-local、subagent-subprocess、sdk package-manager、test-support 各启动器)迁到 `ctx.processes` 上。**作为带有真实设计风险的范围蔓延否决。这些调用点在流与生命周期上的需求存在实质差异:node-pty 所有权(pty)、长生命周期 stdio 上的 LSP 分帧加进程树终止回退(lsp)、以 stdin EOF 打头的 dispose 阶梯和完全不缓冲输出(subagent 传输层)。把它们强行纳入一个按有界批量输出塑形的句柄之下,要么会让这道 seam 膨胀,要么会让句柄与消费方错配。依照「接口围绕当前消费方塑形」的规则,该 seam 在其唯一真实的消费方家族上得到验证后交付;其余调用点已在 seam README 中列为暂缓工作。
+**在同一变更中把仓库其余 spawn 调用点(lsp-local、pty-local、subagent-subprocess、sdk package-manager、test-support 各启动器)迁到 `ctx.subprocess` 上。**作为带有真实设计风险的范围蔓延否决。这些调用点在流与生命周期上的需求存在实质差异:node-pty 所有权(pty)、长生命周期 stdio 上的 LSP 分帧加进程树终止回退(lsp)、以 stdin EOF 打头的 dispose 阶梯和完全不缓冲输出(subagent 传输层)。把它们强行纳入一个按有界批量输出塑形的句柄之下,要么会让这道 seam 膨胀,要么会让句柄与消费方错配。依照「接口围绕当前消费方塑形」的规则,该 seam 在其唯一真实的消费方家族上得到验证后交付;其余调用点已在 seam README 中列为暂缓工作。
**改把 `run_in_background`/任务语义放进进程 seam。**否决:那条边界已经存在。`ctx.tasks` 拥有 id、所有权与通知,bash 工具则把 `BashProcess` 适配成任务钩子。进程 seam 位于 bash 执行器*之下*,而不是与任务注册表并列。
@@ -33,6 +33,6 @@ Status: implemented
## 后果
-换来的是:「运行并管理一个进程」成为一项具备标准三包形态的可替换能力(消费方起步就有两个:`bash-local`、`bash-sandbox`);容器化或远程进程后端可以直接接入,而不触碰 bash 语义;共享的 `DSH_*`/输出词汇有了一个不带 shell 含义的归属;后台进程也能在执行器重载后存活,与任务注册表的存续期模型一致。spawn 管道测试套件整体迁至 `dsh-process-local`(现以 argv 为基础,外加 argv 校验与管理器生命周期/dispose 套件);执行器测试套件如今对着真实管理器固定 bash 所有的各层(分类、合并、spawn 失败提示、归管理器所有的存续期)。
+换来的是:「运行并管理一个进程」成为一项具备标准三包形态的可替换能力(消费方起步就有两个:`bash-local`、`bash-sandbox`);容器化或远程进程后端可以直接接入,而不触碰 bash 语义;共享的 `DSH_*`/输出词汇有了一个不带 shell 含义的归属;后台进程也能在执行器重载后存活,与任务注册表的存续期模型一致。spawn 管道测试套件整体迁至 `dsh-subprocess-local`(现以 argv 为基础,外加 argv 校验与管理器生命周期/dispose 套件);执行器测试套件如今对着真实管理器固定 bash 所有的各层(分类、合并、spawn 失败提示、归管理器所有的存续期)。
-代价是:多出一对包,而且凡加载 bash 执行器之处都多一行组合配置。若某次启动加载了执行器却没有加载管理器,`ctx.bash` 会因等待 `ctx.processes` 而保持挂起(标准的服务缺失行为)。迁移词汇的重导出让 `dsh-bash` 的导入继续可用,但也意味着两个包如今命名同一批类型;进程 seam 是所有者,bash seam 则记录这层重导出。spawn 失败提示经由读取路径变为单次交付,而旧管道曾把它保留在 stderr 缓冲区里,供重复的 `readFrom(0)` 读取;这一点可以接受,因为 bash 的后台读取路径本就是消费游标,该提示能到达唯一存在的那个读取方。
+代价是:多出一对包,而且凡加载 bash 执行器之处都多一行组合配置。若某次启动加载了执行器却没有加载管理器,`ctx.bash` 会因等待 `ctx.subprocess` 而保持挂起(标准的服务缺失行为)。迁移词汇的重导出让 `dsh-bash` 的导入继续可用,但也意味着两个包如今命名同一批类型;进程 seam 是所有者,bash seam 则记录这层重导出。spawn 失败提示经由读取路径变为单次交付,而旧管道曾把它保留在 stderr 缓冲区里,供重复的 `readFrom(0)` 读取;这一点可以接受,因为 bash 的后台读取路径本就是消费游标,该提示能到达唯一存在的那个读取方。
diff --git a/apps/cli/cordis.yml b/apps/cli/cordis.yml
index 5521c09b65..c2ab296385 100644
--- a/apps/cli/cordis.yml
+++ b/apps/cli/cordis.yml
@@ -89,8 +89,8 @@
name: '@deepseek-ai/dsh-workspace'
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash-local
name: '@deepseek-ai/dsh-bash-local'
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 7ef006c924..e21b51e836 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -42,7 +42,7 @@
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
"@deepseek-ai/dsh-session-title": "workspace:^",
diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml
index 5293177942..50abfe37ff 100644
--- a/docs/architecture.i18n.yaml
+++ b/docs/architecture.i18n.yaml
@@ -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
-architecture.md: ca2d1c70342fcef35deca7e247863511fb7c35b8
-architecture.zh.md: c3107b23f96497a5ab2184d660d085f3e8a5e527
+architecture.md: a1e169a9ef7717c6889f5a1aff53af984de565f4
+architecture.zh.md: 025d3b7343b36dca91cca9ebe6323628cb0702f9
diff --git a/docs/architecture.md b/docs/architecture.md
index ca2d1c7034..a1e169a9ef 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -28,7 +28,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts with package-contributed servi
| `ctx.llm` | [`llm/`](../packages/llm/README.md) | adapter registry and streaming model calls |
| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | singleton replay-aware request/surface pressure |
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | foreground/background command execution |
-| `ctx.processes` | [`process/`](../packages/process/README.md) | managed child-process groups under the bash executors |
+| `ctx.subprocess` | [`process/`](../packages/subprocess/README.md) | managed child-process groups under the bash executors |
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | owner-scoped persistent terminal sessions |
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | same-world process confinement (argv wrapping, per-call policy) |
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | shared sandbox policy home |
@@ -178,7 +178,7 @@ New behavior attaches to a documented extension point; a loop change updates thi
|---|---|
| Add a model provider | register an adapter on `ctx.llm` |
| Add a model-facing capability | register on `ctx.tools`; schemas enter prompt assembly |
-| Add shell execution | implement and register a `ctx.bash` backend (the local one spawns through `ctx.processes`) |
+| Add shell execution | implement and register a `ctx.bash` backend (the local one spawns through `ctx.subprocess`) |
| Add persistent terminal execution | register a `ctx.pty` backend and `dsh-tool-pty` |
| Add a human command | register on `ctx.commands`; adapters discover and dispatch it without a model turn |
| Add background work | register on `ctx.tasks`; generic `task_*` tools collect or stop it |
diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md
index c3107b23f9..025d3b7343 100644
--- a/docs/architecture.zh.md
+++ b/docs/architecture.zh.md
@@ -28,7 +28,7 @@
| `ctx.llm` | [`llm/`](../packages/llm/README.md) | 适配器注册表和模型流式调用 |
| `ctx.tokenMeter` | [`llm/token-meter`](../packages/llm/token-meter/README.md) | 感知回放的单实例请求压力和会话表面压力 |
| `ctx.bash` | [`bash/`](../packages/bash/README.md) | 前台和后台命令执行 |
-| `ctx.processes` | [`process/`](../packages/process/README.md) | bash 执行器之下受管理的子进程组 |
+| `ctx.subprocess` | [`process/`](../packages/subprocess/README.md) | bash 执行器之下受管理的子进程组 |
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | 按 owner 隔离的持久化终端会话 |
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 同一执行环境内的进程限制(argv 包装、逐调用策略) |
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | 共享沙箱策略归属点 |
@@ -178,7 +178,7 @@ forever:
|---|---|
| 添加模型提供方 | 在 `ctx.llm` 上注册适配器 |
| 添加面向模型的功能 | 在 `ctx.tools` 上注册;schema 进入提示词组装流程 |
-| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端(本地后端通过 `ctx.processes` 生成进程) |
+| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端(本地后端通过 `ctx.subprocess` 生成进程) |
| 添加持久化终端执行 | 注册 `ctx.pty` 后端和 `dsh-tool-pty` |
| 添加用户命令 | 在 `ctx.commands` 上注册;适配器无需模型轮次即可发现并分派该命令 |
| 添加后台工作 | 在 `ctx.tasks` 上注册;通用 `task_*` 工具负责收集或停止 |
diff --git a/docs/capability-seams.md b/docs/capability-seams.md
index 0fef36c63e..8e0169458f 100644
--- a/docs/capability-seams.md
+++ b/docs/capability-seams.md
@@ -83,7 +83,7 @@ flowchart LR
pkg_goal["goal"]
svc_goals["ctx.goals
Same-session goal domain"]
pkg_process["process"]
- svc_processes["ctx.processes
Process manager seam"]
+ svc_subprocess["ctx.subprocess
Process manager seam"]
pkg_process_local["process-local"]
pkg_bash_local["bash-local"]
pkg_bash_sandbox["bash-sandbox"]
@@ -167,8 +167,8 @@ flowchart LR
pkg_modules --> svc_clientModuleHost
pkg_permission --> svc_permission
pkg_plan_mode --> svc_planMode
- pkg_process --> svc_processes
- pkg_process_local --> svc_processes
+ pkg_process --> svc_subprocess
+ pkg_process_local --> svc_subprocess
pkg_pty --> svc_pty
pkg_pty_local --> svc_pty
pkg_sandbox --> svc_sandbox
@@ -239,8 +239,6 @@ flowchart LR
svc_invariants --> pkg_session
svc_llm --> pkg_agent_loop
svc_llm --> pkg_compact_basic
- svc_processes --> pkg_bash_local
- svc_processes --> pkg_bash_sandbox
svc_pty --> pkg_tool_pty
svc_sandbox --> pkg_bash_sandbox
svc_sandbox --> pkg_pty_local
@@ -270,6 +268,8 @@ flowchart LR
svc_storageDomain --> pkg_workspace
svc_subagents --> pkg_tool_ralph
svc_subagents --> pkg_tool_subagent
+ svc_subprocess --> pkg_bash_local
+ svc_subprocess --> pkg_bash_sandbox
svc_systemPrompt --> pkg_agent_loop
svc_systemPrompt --> pkg_tool_fs
svc_systemPrompt --> pkg_tool_pty
@@ -324,7 +324,7 @@ flowchart LR
| `ctx.agents` | `core` | [`agent`](../packages/core/agent) | - | [`agent-loop`](../packages/core/agent-loop), [`acp`](../packages/acp/acp), [`cli-demo`](../packages/examples/cli-demo), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`tui-demo`](../packages/examples/tui-demo) | - | Owns live Agent handles, the create/resume factory seam, and process-local initiator propagation. |
| `ctx.agentLoop` | `bundle` | [`agent-loop`](../packages/core/agent-loop) | - | [`agent-spine-demo`](../packages/examples/agent-spine-demo) | - | The one concrete loop plugin; extension packages depend on dsh-agent events and services, not on this package. |
| `ctx.goals` | `core` | [`goal`](../packages/goal/goal) | - | - | - | Folds revisioned objective state from the session log and keeps live continuation activation process-local. |
-| `ctx.processes` | `seam` | [`process`](../packages/process/process) | [`process-local`](../packages/process/process-local) | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox) | - | The bash executors spawn their process groups through ctx.processes; the manager owns group lifetime, bounded spill-backed output, and kill escalation. |
+| `ctx.subprocess` | `seam` | `process` | `process-local` | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox) | - | The bash executors spawn their process groups through ctx.subprocess; the service owns group lifetime, bounded spill-backed output, and kill escalation. |
| `ctx.bash` | `seam` | [`bash`](../packages/bash/bash) | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox) | [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) | - | The model-facing bash tools and hook bridges consume this seam; sandboxed or remote executors replace bash-local without touching them. |
| `ctx.bashEnv` | `core` | [`tool-bash`](../packages/bash/tool-bash) | - | - | - | Plugins declare effect-scoped DSH_* facts; tool-bash collects one trusted snapshot per execution and the executor rebuilds the namespace. |
| `ctx.pty` | `seam` | [`pty`](../packages/pty/pty) | [`pty-local`](../packages/pty/pty-local) | [`tool-pty`](../packages/pty/tool-pty) | - | The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-pty exposes the owner-scoped model surface. |
diff --git a/docs/config-catalog.md b/docs/config-catalog.md
index 322f5c70d3..a21b11378d 100644
--- a/docs/config-catalog.md
+++ b/docs/config-catalog.md
@@ -192,7 +192,7 @@ Source: [`packages/examples/agent-spine-demo/src/index.ts:87`](../packages/examp
## `@deepseek-ai/dsh-bash-local`
-Requires: `processes`
+Requires: `subprocess`
```ts config-catalog
/** Plugin config (all optional — `static Config` supplies the defaults). */
@@ -216,7 +216,7 @@ Source: [`packages/bash/bash-local/src/index.ts:39`](../packages/bash/bash-local
## `@deepseek-ai/dsh-bash-sandbox`
-Requires: `processes` · `sandbox` · `sandboxPolicy`
+Requires: `subprocess` · `sandbox` · `sandboxPolicy`
```ts config-catalog
/**
@@ -2055,12 +2055,12 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
- `@deepseek-ai/dsh-goal-session` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-session/src/index.ts`](../packages/goal/goal-session/src/index.ts))
- `@deepseek-ai/dsh-llm` ([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts))
- `@deepseek-ai/dsh-lsp` ([`packages/lsp/lsp/src/index.ts`](../packages/lsp/lsp/src/index.ts))
-- `@deepseek-ai/dsh-process-local` ([`packages/process/process-local/src/index.ts`](../packages/process/process-local/src/index.ts))
- `@deepseek-ai/dsh-pty` ([`packages/pty/pty/src/index.ts`](../packages/pty/pty/src/index.ts))
- `@deepseek-ai/dsh-session` ([`packages/core/session/src/index.ts`](../packages/core/session/src/index.ts))
- `@deepseek-ai/dsh-session-checkpoint-policy` — requires `llm` · `sessionPersistence` · `sessions` · `tools` ([`packages/session-persistence/session-checkpoint-policy/src/index.ts`](../packages/session-persistence/session-checkpoint-policy/src/index.ts))
- `@deepseek-ai/dsh-storage` ([`packages/storage/storage/src/index.ts`](../packages/storage/storage/src/index.ts))
- `@deepseek-ai/dsh-subagent` ([`packages/subagent/subagent/src/index.ts`](../packages/subagent/subagent/src/index.ts))
+- `@deepseek-ai/dsh-subprocess-local` ([`packages/subprocess/subprocess-local/src/index.ts`](../packages/subprocess/subprocess-local/src/index.ts))
- `@deepseek-ai/dsh-tasks-local` ([`packages/tasks/tasks-local/src/index.ts`](../packages/tasks/tasks-local/src/index.ts))
- `@deepseek-ai/dsh-timeout-policy` — requires `tools` ([`packages/timeout/timeout-policy/src/index.ts`](../packages/timeout/timeout-policy/src/index.ts))
- `@deepseek-ai/dsh-tool-ask-user` — requires `tools` · `userInteraction` ([`packages/ui/tool-ask-user/src/index.ts`](../packages/ui/tool-ask-user/src/index.ts))
@@ -2076,11 +2076,11 @@ Abstract service classes — a deployment loads a concrete implementation packag
- `@deepseek-ai/dsh-code-runtime` — abstract `CodeRuntime` ([`packages/code-runtime/code-runtime/src/index.ts`](../packages/code-runtime/code-runtime/src/index.ts))
- `@deepseek-ai/dsh-compact` — abstract `CompactService` ([`packages/compact/compact/src/index.ts`](../packages/compact/compact/src/index.ts))
- `@deepseek-ai/dsh-fs` — abstract `FileSystem` ([`packages/fs/fs/src/index.ts`](../packages/fs/fs/src/index.ts))
-- `@deepseek-ai/dsh-process` — abstract `ProcessManager` ([`packages/process/process/src/index.ts`](../packages/process/process/src/index.ts))
- `@deepseek-ai/dsh-sandbox` — abstract `SandboxProvider` ([`packages/sandbox/sandbox/src/index.ts`](../packages/sandbox/sandbox/src/index.ts))
- `@deepseek-ai/dsh-session-persistence` — abstract `SessionPersistence` ([`packages/session-persistence/session-persistence/src/index.ts`](../packages/session-persistence/session-persistence/src/index.ts))
- `@deepseek-ai/dsh-session-query` — abstract `SessionQueryService` ([`packages/session-query/session-query/src/index.ts`](../packages/session-query/session-query/src/index.ts))
- `@deepseek-ai/dsh-spill` — abstract `SpillStore` ([`packages/spill/spill/src/index.ts`](../packages/spill/spill/src/index.ts))
+- `@deepseek-ai/dsh-subprocess` — abstract `SubprocessService` ([`packages/subprocess/subprocess/src/index.ts`](../packages/subprocess/subprocess/src/index.ts))
- `@deepseek-ai/dsh-tasks` — abstract `TaskService` ([`packages/tasks/tasks/src/index.ts`](../packages/tasks/tasks/src/index.ts))
- `@deepseek-ai/dsh-workflow` — abstract `WorkflowService` ([`packages/workflow/workflow/src/index.ts`](../packages/workflow/workflow/src/index.ts))
diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md
index b2229130bc..4108332e43 100644
--- a/docs/cordis-catalog/services.md
+++ b/docs/cordis-catalog/services.md
@@ -257,7 +257,7 @@ Implementations must honor these semantics:
- run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a BashRunResult.
- start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr.
- BashProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files.
-- A still-running background process is stopped and awaited when its owning composition tears down. With the process-manager seam that boundary is `ctx.processes` disposal, so a background process survives an executor-only reload.
+- A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload.
```ts cordis-catalog
/**
@@ -315,7 +315,7 @@ collect(execution: ToolExecution): DshEnvironment
list(): BashEnvVariableInfo[]
```
-Types: [DshEnvironment](../core-data-structures/process.md) · [ToolExecution](../core-data-structures/tools.md)
+Types: [DshEnvironment](../core-data-structures/subprocess.md) · [ToolExecution](../core-data-structures/tools.md)
Source: [`packages/bash/tool-bash/src/index.ts:104`](../../packages/bash/tool-bash/src/index.ts)
@@ -829,31 +829,6 @@ Types: [Agent](../core-data-structures/core.md)
Source: [`packages/plan/plan-mode/src/index.ts:141`](../../packages/plan/plan-mode/src/index.ts)
-## `ctx.processes` — `ProcessManager` (abstract seam)
-
-Abstract process manager. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.processes` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior).
-
-Implementations must honor these semantics:
-
-- spawn returns immediately with a live handle; `done` resolves at process close and rejects only for spawn-level failures.
-- Output readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists.
-- ProcessHandle.kill and the spec's abort signal escalate SIGTERM→grace→SIGKILL across the whole process group.
-- Disposal kills all still-running managed processes and awaits their exit.
-
-```ts cordis-catalog
-/**
- * Start one managed child process from a fully-specified spec; this seam
- * applies no defaults.
- * @param spec - argv, directory, limits, grace, cancellation, and environment.
- * @returns the live process handle (readers, kill, outcome promise).
- */
-abstract spawn(spec: ProcessSpawnSpec): ProcessHandle
-```
-
-Types: [ProcessHandle](../core-data-structures/process.md) · [ProcessSpawnSpec](../core-data-structures/process.md)
-
-Source: [`packages/process/process/src/index.ts:48`](../../packages/process/process/src/index.ts)
-
## `ctx.pty` — `PtyService`
In-process registry for replaceable PTY backends and exact-Agent sessions.
@@ -1584,6 +1559,31 @@ Types: [SubagentProvider](../core-data-structures/subagent.md) · [SubagentRun](
Source: [`packages/subagent/subagent/src/index.ts:180`](../../packages/subagent/subagent/src/index.ts)
+## `ctx.subprocess` — `SubprocessService` (abstract seam)
+
+Abstract subprocess service. Subclass, implement spawn, and load the subclass as a plugin — it registers as `ctx.subprocess` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior).
+
+Implementations must honor these semantics:
+
+- spawn returns immediately with a live handle; `done` resolves at process close and rejects only for spawn-level failures.
+- Output readers are offset-based and non-consuming, so independent readers never consume one another's output; lossy reads report truncation and the spill file holding the complete stream when one exists.
+- SubprocessHandle.kill and the spec's abort signal escalate SIGTERM→grace→SIGKILL across the whole process group.
+- Disposal kills all still-running managed processes and awaits their exit.
+
+```ts cordis-catalog
+/**
+ * Start one managed child process from a fully-specified spec; this seam
+ * applies no defaults.
+ * @param spec - argv, directory, limits, grace, cancellation, and environment.
+ * @returns the live process handle (readers, kill, outcome promise).
+ */
+abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle
+```
+
+Types: [SubprocessHandle](../core-data-structures/subprocess.md) · [SubprocessSpawnSpec](../core-data-structures/subprocess.md)
+
+Source: [`packages/subprocess/subprocess/src/index.ts:48`](../../packages/subprocess/subprocess/src/index.ts)
+
## `ctx.systemPrompt` — `SystemPrompt`
Registry service for the prompt inputs assembled before each model step.
diff --git a/docs/core-data-structures/bash.i18n.yaml b/docs/core-data-structures/bash.i18n.yaml
index 00a77885e8..fba715d163 100644
--- a/docs/core-data-structures/bash.i18n.yaml
+++ b/docs/core-data-structures/bash.i18n.yaml
@@ -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
-bash.md: 0c92addf4778fa780cd432444549bfc36eeb763c
-bash.zh.md: cd9d94055627937d03107ead5a4ffaeaa945d187
+bash.md: 4ea0ace7e4af8cda3ff5bf1b4fc672f6425e5396
+bash.zh.md: 6642c85134748bceaa4a7783742a63da8b6acabd
diff --git a/docs/core-data-structures/bash.md b/docs/core-data-structures/bash.md
index 0c92addf47..4ea0ace7e4 100644
--- a/docs/core-data-structures/bash.md
+++ b/docs/core-data-structures/bash.md
@@ -2,13 +2,13 @@
English | [中文](bash.zh.md)
-The bash execution seam is split across interface ([dsh-bash](../../packages/bash/bash), `ctx.bash`), implementations ([dsh-bash-local](../../packages/bash/bash-local) and [dsh-bash-sandbox](../../packages/bash/bash-sandbox)), and consumer ([dsh-tool-bash](../../packages/bash/tool-bash), the `bash` schema). Generic background-task ids, ownership, and controls live in [tasks.md](tasks.md); this seam returns a task-free process handle. Raw process-group mechanics live behind the [process-manager seam](process.md).
+The bash execution seam is split across interface ([dsh-bash](../../packages/bash/bash), `ctx.bash`), implementations ([dsh-bash-local](../../packages/bash/bash-local) and [dsh-bash-sandbox](../../packages/bash/bash-sandbox)), and consumer ([dsh-tool-bash](../../packages/bash/tool-bash), the `bash` schema). Generic background-task ids, ownership, and controls live in [tasks.md](tasks.md); this seam returns a task-free process handle. Raw process-group mechanics live behind the [subprocess seam](subprocess.md).
Source: [`packages/bash/bash/src/types.ts`](../../packages/bash/bash/src/types.ts)
## Managed shell environment namespace
-`DSH_*` variables are Harness-owned child-process facts. The model-facing bash tool collects them through `ctx.bashEnv` and passes them through `BashExecRequest.dshEnv`; the process manager removes inherited `DSH_*` names before merging the current snapshot. The `DshEnvironmentKey`/`DshEnvironment` vocabulary is owned by the [process-manager seam](process.md) and re-exported by `dsh-bash`.
+`DSH_*` variables are Harness-owned child-process facts. The model-facing bash tool collects them through `ctx.bashEnv` and passes them through `BashExecRequest.dshEnv`; the subprocess service removes inherited `DSH_*` names before merging the current snapshot. The `DshEnvironmentKey`/`DshEnvironment` vocabulary is owned by the [subprocess seam](subprocess.md) and re-exported by `dsh-bash`.
## Request vs. spec: the `resolve()` split
@@ -135,7 +135,7 @@ interface BashRunResult {
}
```
-Each stream is a `CollectedOutput` — the (possibly truncated) text plus recovery info; when truncated, `text` is the **tail** and the complete stream spills to a private file. The shape is owned by the [process-manager seam](process.md) and re-exported by `dsh-bash`.
+Each stream is a `CollectedOutput` — the (possibly truncated) text plus recovery info; when truncated, `text` is the **tail** and the complete stream spills to a private file. The shape is owned by the [subprocess seam](subprocess.md) and re-exported by `dsh-bash`.
## File sandbox: `BashSandboxInfo`
@@ -171,7 +171,7 @@ One more piece completes the vocabulary: the `SANDBOX_UNAVAILABLE` error code (o
/**
* A background process handle returned by {@link BashExecutor.start}. It is the
* only access path; buffered output remains readable after exit. Composition
- * teardown (the process manager's disposal) kills running processes and
+ * teardown (the subprocess service's disposal) kills running processes and
* awaits {@link done}; an executor-only reload leaves them running.
*/
interface BashProcess {
@@ -217,4 +217,4 @@ interface BashProcessRead {
## The service
-`BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns command defaulting, timeout/abort classification, the terminal environment, and the background read merge; process groups, bounded collectors, spill files, credential scrubbing, and disposal quiescence are the [process manager](process.md)'s. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic task runtime](tasks.md).
+`BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns command defaulting, timeout/abort classification, the terminal environment, and the background read merge; process groups, bounded collectors, spill files, credential scrubbing, and disposal quiescence are the [subprocess service](subprocess.md)'s. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic task runtime](tasks.md).
diff --git a/docs/core-data-structures/bash.zh.md b/docs/core-data-structures/bash.zh.md
index cd9d940556..6642c85134 100644
--- a/docs/core-data-structures/bash.zh.md
+++ b/docs/core-data-structures/bash.zh.md
@@ -2,13 +2,13 @@
[English](bash.md) | 中文
-bash 执行 seam 分为接口([dsh-bash](../../packages/bash/bash),`ctx.bash`)、实现([dsh-bash-local](../../packages/bash/bash-local) 与 [dsh-bash-sandbox](../../packages/bash/bash-sandbox))和消费方([dsh-tool-bash](../../packages/bash/tool-bash),即 `bash` schema)。通用后台任务的 id、所有权与控制位于 [tasks.md](tasks.md);本 seam 返回一个不含任务概念的进程句柄。原始进程组机制位于[进程管理器 seam](process.md)之后。
+bash 执行 seam 分为接口([dsh-bash](../../packages/bash/bash),`ctx.bash`)、实现([dsh-bash-local](../../packages/bash/bash-local) 与 [dsh-bash-sandbox](../../packages/bash/bash-sandbox))和消费方([dsh-tool-bash](../../packages/bash/tool-bash),即 `bash` schema)。通用后台任务的 id、所有权与控制位于 [tasks.md](tasks.md);本 seam 返回一个不含任务概念的进程句柄。原始进程组机制位于[进程管理器 seam](subprocess.md)之后。
源码:[`packages/bash/bash/src/types.ts`](../../packages/bash/bash/src/types.ts)
## 受管 shell 环境命名空间
-`DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.bashEnv` 收集它们,再经由 `BashExecRequest.dshEnv` 传递;进程管理器在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[进程管理器 seam](process.md)所有,由 `dsh-bash` 重导出。
+`DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.bashEnv` 收集它们,再经由 `BashExecRequest.dshEnv` 传递;进程管理器在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
## 请求与规格:`resolve()` 拆分
@@ -135,7 +135,7 @@ interface BashRunResult {
}
```
-每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。该形状归[进程管理器 seam](process.md)所有,由 `dsh-bash` 重导出。
+每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。该形状归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
## 文件沙箱:`BashSandboxInfo`
@@ -171,7 +171,7 @@ interface BashSandboxInfo {
/**
* A background process handle returned by {@link BashExecutor.start}. It is the
* only access path; buffered output remains readable after exit. Composition
- * teardown (the process manager's disposal) kills running processes and
+ * teardown (the subprocess service's disposal) kills running processes and
* awaits {@link done}; an executor-only reload leaves them running.
*/
interface BashProcess {
@@ -217,4 +217,4 @@ interface BashProcessRead {
## 服务
-`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](process.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。
+`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。
diff --git a/docs/core-data-structures/core.i18n.yaml b/docs/core-data-structures/core.i18n.yaml
index 47310dd09b..3ff88fcca2 100644
--- a/docs/core-data-structures/core.i18n.yaml
+++ b/docs/core-data-structures/core.i18n.yaml
@@ -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
-core.md: 781267cccdb5bbda33e5be6a9e807fdbe47dbc83
-core.zh.md: d0f67983b98b0cf679a8e599a5f8ab3c64490dd0
+core.md: cdbf8f2f7a4484986abe56511698ae3f4c2096b4
+core.zh.md: 2afd9b02135a0cc54a48ca9404be4d55f5b8c64a
diff --git a/docs/core-data-structures/core.md b/docs/core-data-structures/core.md
index 781267cccd..cdbf8f2f7a 100644
--- a/docs/core-data-structures/core.md
+++ b/docs/core-data-structures/core.md
@@ -31,6 +31,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
| [user-interaction.md](user-interaction.md) | the UI-backed human question/answer seam: `AskUserQuestionRequest`, answer/options vocabulary, provider API, error taxonomy |
| [approval.md](approval.md) | the one-shot user-approval seam: `ApprovalRequest`, `ApprovalOutcome`, per-session policy, audit and answerer contracts |
| [bash.md](bash.md) | the bash executor seam: `BashExecRequest`/`Spec`, `BashRunResult`, background `BashProcess` handles |
+| [subprocess.md](subprocess.md) | the subprocess seam: fully-explicit `SubprocessSpawnSpec`, offset-based output readers, unclassified `SubprocessOutcome`, and the managed `DSH_*` environment vocabulary |
| [pty.md](pty.md) | persistent terminal ids, backend/session contracts, send readiness, bounded reads, and owner-visible snapshots |
| [sandbox.md](sandbox.md) | per-session policy resolution and the process-confinement seam: file-effect modes, execution/provider policies, `ConfinedArgv`, enforcement and fail-closed errors |
| [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
diff --git a/docs/core-data-structures/core.zh.md b/docs/core-data-structures/core.zh.md
index d0f67983b9..2afd9b0213 100644
--- a/docs/core-data-structures/core.zh.md
+++ b/docs/core-data-structures/core.zh.md
@@ -31,6 +31,7 @@ harness 是一个微内核:一个极小的核心加上众多插件。大多数
| [user-interaction.md](user-interaction.md) | UI 支持的人工问答 seam:`AskUserQuestionRequest`、answer/options 词汇、提供方 API、错误分类体系 |
| [approval.md](approval.md) | 一次性用户审批 seam:`ApprovalRequest`、`ApprovalOutcome`、逐会话策略、审计与 answerer 契约 |
| [bash.md](bash.md) | bash 执行器 seam:`BashExecRequest`/`Spec`、`BashRunResult`、后台 `BashProcess` 句柄 |
+| [subprocess.md](subprocess.md) | 子进程 seam:完全显式的 `SubprocessSpawnSpec`、基于偏移的输出读取器、不含分类的 `SubprocessOutcome`,以及受管 `DSH_*` 环境词汇 |
| [pty.md](pty.md) | 持久化终端 ID、后端/会话契约、发送就绪状态、有界读取与 owner 可见快照 |
| [sandbox.md](sandbox.md) | 每会话策略解析与进程约束 seam:文件效果模式、执行/提供方策略、`ConfinedArgv`、强制执行与故障关闭错误 |
| [code-runtime.md](code-runtime.md) | 代码执行 seam:`CodeRunRequest`/`Result`、绑定命名空间、捕获日志、`CodeRunFailure` 分类体系 |
diff --git a/docs/core-data-structures/process.md b/docs/core-data-structures/subprocess.md
similarity index 59%
rename from docs/core-data-structures/process.md
rename to docs/core-data-structures/subprocess.md
index 93e7a73ad6..6e7cea3990 100644
--- a/docs/core-data-structures/process.md
+++ b/docs/core-data-structures/subprocess.md
@@ -1,21 +1,47 @@
-# Process Manager
+# Subprocess
-The child-process manager seam is split across interface ([dsh-process](../../packages/process/process), `ctx.processes`) and implementation ([dsh-process-local](../../packages/process/process-local)); its consumers are other capability seams — today the [bash executor family](bash.md), which passes `['bash', '-c', command]` argv and owns every default. This seam owns the managed `DSH_*` environment namespace and the `CollectedOutput` shape; [dsh-bash](../../packages/bash/bash) re-exports them so bash consumers keep one import root.
+The subprocess seam is split across interface ([dsh-subprocess](../../packages/subprocess/subprocess), `ctx.subprocess`) and implementation ([dsh-subprocess-local](../../packages/subprocess/subprocess-local)); its consumers are other capability seams — today the [bash executor family](bash.md), which passes `['bash', '-c', command]` argv and owns every default. This seam owns the managed `DSH_*` environment namespace and the `CollectedOutput` shape; [dsh-bash](../../packages/bash/bash) re-exports them so bash consumers keep one import root.
-Source: [`packages/process/process/src/types.ts`](../../packages/process/process/src/types.ts)
+Source: [`packages/subprocess/subprocess/src/types.ts`](../../packages/subprocess/subprocess/src/types.ts)
+
+## Managed environment namespace and captured output
+
+`DSH_*` variables are Harness-owned child-process facts; implementations discard ambient `DSH_*` names before merging the caller's snapshot, and each captured stream reports its truncation and spill-recovery state through `CollectedOutput`.
+
+```ts type-equiv
+/** One environment key inside the managed {@link DSH_ENV_PREFIX} namespace. */
+type DshEnvironmentKey = `${typeof DSH_ENV_PREFIX}${string}`
+```
+
+```ts type-equiv
+/** Trusted DeepSeek Harness variables for one child-process execution. */
+type DshEnvironment = Readonly>
+```
+
+```ts type-equiv
+/** One captured stream: the (possibly truncated) text plus recovery info. */
+interface CollectedOutput {
+ /** Collected text — the TAIL of the stream when truncated. */
+ text: string
+ /** True when bytes were dropped from `text`. */
+ truncated: boolean
+ /** Path to a file holding the COMPLETE stream, when truncated and available. */
+ spillPath?: string
+}
+```
## The fully-explicit spawn spec
-The seam applies no defaults: every limit and directory is explicit on the spec, so the caller's own config — not a hidden process-manager default — decides them. `argv` is never shell-interpreted.
+The seam applies no defaults: every limit and directory is explicit on the spec, so the caller's own config — not a hidden subprocess-service default — decides them. `argv` is never shell-interpreted.
```ts type-equiv
/**
* A fully-specified spawn request. This seam applies no defaults: every limit
* and directory is explicit, so the caller's own config — not a hidden
- * process-manager default — decides them (the `dsh-bash` request/spec split
+ * subprocess-service default — decides them (the `dsh-bash` request/spec split
* is the owning template).
*/
-interface ProcessSpawnSpec {
+interface SubprocessSpawnSpec {
/** Executable and arguments; `argv[0]` is the program. Never shell-interpreted here. */
argv: readonly string[]
/** Working directory for the child. */
@@ -62,15 +88,15 @@ A spawn returns a live handle immediately. Output readers take whole-stream byte
* A live child process. `kill()` starts the group SIGTERM→grace→SIGKILL
* escalation; buffered output remains readable after exit.
*/
-interface ProcessHandle {
+interface SubprocessHandle {
/** Process id (group leader); -1 when the spawn itself failed. */
readonly pid: number
/** Live stdout reader (also readable after exit). */
- readonly stdout: ProcessOutputReader
+ readonly stdout: SubprocessOutputReader
/** Live stderr reader (also readable after exit). */
- readonly stderr: ProcessOutputReader
+ readonly stderr: SubprocessOutputReader
/** Resolves when the process closes; rejects only for spawn-level failures. */
- readonly done: Promise
+ readonly done: Promise
/** Begin SIGTERM→grace→SIGKILL on the process group. Idempotent. */
kill(): void
}
@@ -82,7 +108,7 @@ interface ProcessHandle {
* whole-stream byte coordinates owned by the caller, so independent readers
* cannot consume one another's output.
*/
-interface ProcessOutputReader {
+interface SubprocessOutputReader {
/**
* Read everything captured since `fromByte`. When that offset has slid out
* of the in-memory tail window the read is `lossy` — it returns the whole
@@ -90,13 +116,13 @@ interface ProcessOutputReader {
* @param fromByte - whole-stream offset to resume from (a prior read's `nextOffset`; 0 for the first read).
* @returns the delta text, the next offset, the `lossy` flag, and the spill path when one exists.
*/
- readFrom(fromByte: number): ProcessOutputRead
+ readFrom(fromByte: number): SubprocessOutputRead
}
```
```ts type-equiv
-/** One incremental {@link ProcessOutputReader.readFrom} read. */
-interface ProcessOutputRead {
+/** One incremental {@link SubprocessOutputReader.readFrom} read. */
+interface SubprocessOutputRead {
/** Stream text from the requested offset (the whole retained tail when lossy). */
text: string
/** Whole-stream offset to resume from on the next read. */
@@ -110,15 +136,15 @@ interface ProcessOutputRead {
## Outcomes carry no cause classification
-`done` reports raw exit facts. The manager kills on abort but never decides why — the caller reads the deadline signal it owns to classify timeout versus cancellation (the bash executor's `timedOut`/`aborted` split).
+`done` reports raw exit facts. The service kills on abort but never decides why — the caller reads the deadline signal it owns to classify timeout versus cancellation (the bash executor's `timedOut`/`aborted` split).
```ts type-equiv
/**
* Raw outcome of one closed process. Deliberately carries NO timeout or
- * cancellation classification: the manager kills on abort but does not decide
+ * cancellation classification: the service kills on abort but does not decide
* why — the caller reads the signal it owns to classify causes.
*/
-interface ProcessOutcome {
+interface SubprocessOutcome {
/** Exit code; null when the process died from a signal. */
exitCode: number | null
/** Terminating signal (e.g. 'SIGTERM'); null on normal exit. */
@@ -130,4 +156,4 @@ interface ProcessOutcome {
## Service behavior
-The abstract [`ProcessManager`](../../packages/process/process/src/index.ts) seam defines `spawn` only; [`LocalProcessManager`](../../packages/process/process-local/src/index.ts) is the local implementation (detached groups, tail-keep spill-backed collection, credential scrub, kill-and-join disposal). See [`dsh-process`](../../packages/process/process/README.md) for the seam contract and [`dsh-process-local`](../../packages/process/process-local/README.md) for the mechanics.
+The abstract [`SubprocessService`](../../packages/subprocess/subprocess/src/index.ts) seam defines `spawn` only; [`LocalSubprocessService`](../../packages/subprocess/subprocess-local/src/index.ts) is the local implementation (detached groups, tail-keep spill-backed collection, credential scrub, kill-and-join disposal). See [`dsh-subprocess`](../../packages/subprocess/subprocess/README.md) for the seam contract and [`dsh-subprocess-local`](../../packages/subprocess/subprocess-local/README.md) for the mechanics.
diff --git a/docs/module-graph.md b/docs/module-graph.md
index 10e6ab65a6..f310cac386 100644
--- a/docs/module-graph.md
+++ b/docs/module-graph.md
@@ -184,10 +184,6 @@ flowchart TD
subgraph group_mcp["packages/mcp"]
pkg_mcp_client["mcp-client"]
end
- subgraph group_process["packages/process"]
- pkg_process["process"]
- pkg_process_local["process-local"]
- end
subgraph group_pty["packages/pty"]
pkg_pty["pty"]
pkg_pty_local["pty-local"]
@@ -209,6 +205,10 @@ flowchart TD
pkg_storage_json["storage-json"]
pkg_storage_sqlite["storage-sqlite"]
end
+ subgraph group_subprocess["packages/subprocess"]
+ pkg_subprocess["subprocess"]
+ pkg_subprocess_local["subprocess-local"]
+ end
subgraph group_tasks["packages/tasks"]
pkg_tasks["tasks"]
pkg_tasks_local["tasks-local"]
@@ -247,8 +247,8 @@ flowchart TD
pkg_jsonrpc_demo --> pkg_invariants
pkg_host_apiproxy --> pkg_invariants
pkg_host_webserver --> pkg_invariants
- pkg_process --> pkg_invariants
pkg_storage --> pkg_invariants
+ pkg_subprocess --> pkg_invariants
pkg_llm --> pkg_brand
pkg_llm --> pkg_invariants
pkg_client_connection --> pkg_host_webserver
@@ -271,8 +271,6 @@ flowchart TD
pkg_client_ui_workspace --> pkg_client_ui_primitives
pkg_client_ui_workspace --> pkg_client_ui_slots
pkg_client_ui_workspace --> pkg_invariants
- pkg_process_local --> pkg_invariants
- pkg_process_local --> pkg_process
pkg_helper --> pkg_brand
pkg_helper --> pkg_invariants
pkg_telemetry --> pkg_brand
@@ -284,6 +282,8 @@ flowchart TD
pkg_storage_json --> pkg_storage
pkg_storage_sqlite --> pkg_invariants
pkg_storage_sqlite --> pkg_storage
+ pkg_subprocess_local --> pkg_invariants
+ pkg_subprocess_local --> pkg_subprocess
pkg_llm_deepseek --> pkg_invariants
pkg_llm_deepseek --> pkg_llm
pkg_llm_deepseek --> pkg_timeout
@@ -314,8 +314,8 @@ flowchart TD
pkg_agent --> pkg_session
pkg_agent --> pkg_system_prompt
pkg_bash --> pkg_invariants
- pkg_bash --> pkg_process
pkg_bash --> pkg_sandbox
+ pkg_bash --> pkg_subprocess
pkg_fs --> pkg_brand
pkg_fs --> pkg_invariants
pkg_fs --> pkg_llm
@@ -379,7 +379,7 @@ flowchart TD
pkg_goal --> pkg_session
pkg_bash_local --> pkg_bash
pkg_bash_local --> pkg_invariants
- pkg_bash_local --> pkg_process
+ pkg_bash_local --> pkg_subprocess
pkg_bash_local --> pkg_timeout
pkg_fs_local --> pkg_fs
pkg_fs_local --> pkg_invariants
@@ -837,8 +837,8 @@ flowchart TD
| [`jsonrpc-demo`](../packages/examples/jsonrpc-demo) | `examples` | [`invariants`](../packages/support/invariants) |
| [`host-apiproxy`](../packages/host/apiproxy) | `host` | [`invariants`](../packages/support/invariants) |
| [`host-webserver`](../packages/host/webserver) | `host` | [`invariants`](../packages/support/invariants) |
-| [`process`](../packages/process/process) | `process` | [`invariants`](../packages/support/invariants) |
| [`storage`](../packages/storage/storage) | `storage` | [`invariants`](../packages/support/invariants) |
+| [`subprocess`](../packages/subprocess/subprocess) | `subprocess` | [`invariants`](../packages/support/invariants) |
| [`llm`](../packages/llm/llm) | `llm` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
| [`client-connection`](../packages/client/connection) | `client` | [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
| [`client-hmr`](../packages/client/hmr) | `client` | [`client-modules`](../packages/client/modules), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) |
@@ -846,12 +846,12 @@ flowchart TD
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-sidebar`](../packages/client/ui-sidebar) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
| [`client-ui-workspace`](../packages/client/ui-workspace) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) |
-| [`process-local`](../packages/process/process-local) | `process` | [`invariants`](../packages/support/invariants), [`process`](../packages/process/process) |
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
| [`telemetry`](../packages/sdk/telemetry) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) |
| [`storage-domain`](../packages/storage/storage-domain) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) |
| [`storage-json`](../packages/storage/storage-json) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) |
| [`storage-sqlite`](../packages/storage/storage-sqlite) | `storage` | [`invariants`](../packages/support/invariants), [`storage`](../packages/storage/storage) |
+| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess) |
| [`llm-deepseek`](../packages/llm/llm-deepseek) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
| [`llm-pi-ai`](../packages/llm/llm-pi-ai) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`timeout`](../packages/util/timeout) |
| [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) |
@@ -861,7 +861,7 @@ flowchart TD
| [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) |
| [`token-meter`](../packages/llm/token-meter) | `llm` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`agent`](../packages/core/agent) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt) |
-| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`process`](../packages/process/process), [`sandbox`](../packages/sandbox/sandbox) |
+| [`bash`](../packages/bash/bash) | `bash` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`subprocess`](../packages/subprocess/subprocess) |
| [`fs`](../packages/fs/fs) | `fs` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
| [`compact`](../packages/compact/compact) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
| [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | `compact` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session) |
@@ -880,7 +880,7 @@ flowchart TD
| [`sandbox-policy`](../packages/sandbox/sandbox-policy) | `sandbox` | [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
| [`llm-retry`](../packages/llm/llm-retry) | `llm` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`timeout`](../packages/util/timeout) |
| [`goal`](../packages/goal/goal) | `goal` | [`agent`](../packages/core/agent), [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session) |
-| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`process`](../packages/process/process), [`timeout`](../packages/util/timeout) |
+| [`bash-local`](../packages/bash/bash-local) | `bash` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
| [`fs-local`](../packages/fs/fs-local) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
| [`fs-policy`](../packages/fs/fs-policy) | `fs` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants) |
| [`skill-local`](../packages/skill/skill-local) | `skill` | [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`skill`](../packages/skill/skill) |
diff --git a/examples/acp-agent/composition.md b/examples/acp-agent/composition.md
index e4660ed8da..b4a3236920 100644
--- a/examples/acp-agent/composition.md
+++ b/examples/acp-agent/composition.md
@@ -14,8 +14,8 @@ flowchart LR
cfg --> plugin_acp_sandbox
plugin_acp_sandbox_policy["sandbox-policy
@deepseek-ai/dsh-sandbox-policy"]
cfg --> plugin_acp_sandbox_policy
- plugin_acp_processes["processes
@deepseek-ai/dsh-process-local"]
- cfg --> plugin_acp_processes
+ plugin_acp_subprocess["subprocess
@deepseek-ai/dsh-subprocess-local"]
+ cfg --> plugin_acp_subprocess
plugin_acp_bash["bash
@deepseek-ai/dsh-bash-sandbox"]
cfg --> plugin_acp_bash
plugin_acp_approval["approval
@deepseek-ai/dsh-user-approval"]
@@ -70,7 +70,7 @@ flowchart LR
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
| `sandbox` | `@deepseek-ai/dsh-sandbox-local` |
| `sandbox-policy` | `@deepseek-ai/dsh-sandbox-policy` |
-| `processes` | `@deepseek-ai/dsh-process-local` |
+| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `bash` | `@deepseek-ai/dsh-bash-sandbox` |
| `approval` | `@deepseek-ai/dsh-user-approval` |
| `acp-agent` | `@deepseek-ai/dsh-acp-demo` |
diff --git a/examples/acp-agent/cordis.yml b/examples/acp-agent/cordis.yml
index 38994b1826..f784972429 100644
--- a/examples/acp-agent/cordis.yml
+++ b/examples/acp-agent/cordis.yml
@@ -34,8 +34,8 @@
workspaceRoot: !!js process.cwd()
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-sandbox'
diff --git a/examples/cordis-agent/composition.md b/examples/cordis-agent/composition.md
index 78eb186e37..55e7f33b2c 100644
--- a/examples/cordis-agent/composition.md
+++ b/examples/cordis-agent/composition.md
@@ -12,8 +12,8 @@ flowchart LR
cfg --> plugin_cordis_hmr
plugin_cordis_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"]
cfg --> plugin_cordis_llm_deepseek
- plugin_cordis_processes["processes
@deepseek-ai/dsh-process-local"]
- cfg --> plugin_cordis_processes
+ plugin_cordis_subprocess["subprocess
@deepseek-ai/dsh-subprocess-local"]
+ cfg --> plugin_cordis_subprocess
plugin_cordis_bash["bash
@deepseek-ai/dsh-bash-local"]
cfg --> plugin_cordis_bash
plugin_cordis_fs_local["fs-local
@deepseek-ai/dsh-fs-local"]
@@ -41,7 +41,7 @@ flowchart LR
| --- | --- |
| `hmr` | `@cordisjs/plugin-hmr` |
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `processes` | `@deepseek-ai/dsh-process-local` |
+| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `bash` | `@deepseek-ai/dsh-bash-local` |
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `web` | `@deepseek-ai/dsh-web` |
diff --git a/examples/cordis-agent/cordis.yml b/examples/cordis-agent/cordis.yml
index 051c3a5662..369886c9e5 100644
--- a/examples/cordis-agent/cordis.yml
+++ b/examples/cordis-agent/cordis.yml
@@ -26,8 +26,8 @@
# Local bash executor for agent-spine-demo's tool-bash schema — gives the agent an
# ordinary tool whose calls make the mounted listeners observably fire.
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/headless-agent/composition.md b/examples/headless-agent/composition.md
index 98ef6cd167..53a01260e1 100644
--- a/examples/headless-agent/composition.md
+++ b/examples/headless-agent/composition.md
@@ -10,8 +10,8 @@ flowchart LR
cfg["examples/headless-agent
cordis.yml"]
plugin_headless_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"]
cfg --> plugin_headless_llm_deepseek
- plugin_headless_processes["processes
@deepseek-ai/dsh-process-local"]
- cfg --> plugin_headless_processes
+ plugin_headless_subprocess["subprocess
@deepseek-ai/dsh-subprocess-local"]
+ cfg --> plugin_headless_subprocess
plugin_headless_bash["bash
@deepseek-ai/dsh-bash-local"]
cfg --> plugin_headless_bash
plugin_headless_cli_agent["cli-agent
@deepseek-ai/dsh-cli-demo"]
@@ -56,7 +56,7 @@ flowchart LR
| Plugin id | Package / module |
| --- | --- |
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `processes` | `@deepseek-ai/dsh-process-local` |
+| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `bash` | `@deepseek-ai/dsh-bash-local` |
| `cli-agent` | `@deepseek-ai/dsh-cli-demo` |
| `token-meter` | `@deepseek-ai/dsh-token-meter` |
diff --git a/examples/headless-agent/cordis.yml b/examples/headless-agent/cordis.yml
index 261eaa6d20..896c73469b 100644
--- a/examples/headless-agent/cordis.yml
+++ b/examples/headless-agent/cordis.yml
@@ -20,8 +20,8 @@
contextWindow: 128000
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/headless-agent/semantic-checkpoint.cordis.snapshot.yml b/examples/headless-agent/semantic-checkpoint.cordis.snapshot.yml
index 66fbca2e6a..432b64eb9f 100644
--- a/examples/headless-agent/semantic-checkpoint.cordis.snapshot.yml
+++ b/examples/headless-agent/semantic-checkpoint.cordis.snapshot.yml
@@ -18,8 +18,8 @@
overrideFile: !!js process.env.DSH_SNAPSHOT_OVERRIDE
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/headless-agent/tests/code-mode.e2e.ts b/examples/headless-agent/tests/code-mode.e2e.ts
index 6f36b1f8c9..31e029f201 100644
--- a/examples/headless-agent/tests/code-mode.e2e.ts
+++ b/examples/headless-agent/tests/code-mode.e2e.ts
@@ -13,7 +13,7 @@ import AgentRegistry, { type Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
@@ -56,7 +56,7 @@ async function codeModeHarness(cwd: string): Promise {
await harness.plugin(AgentRegistry)
await harness.plugin(AgentLoop, { agents: [] })
await harness.plugin(LlmDeepSeek)
- await harness.plugin(LocalProcessManager)
+ await harness.plugin(LocalSubprocessService)
await harness.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 })
await harness.plugin(ToolBash)
await harness.plugin(WorkerCodeRuntime, {})
@@ -116,7 +116,7 @@ async function backgroundCodeModeHarness(cwd: string): Promise {
const harness = await typedCodeModeHarness()
await harness.plugin(LocalTaskService)
await harness.plugin(ToolTasks, {})
- await harness.plugin(LocalProcessManager)
+ await harness.plugin(LocalSubprocessService)
await harness.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 })
await harness.plugin(ToolBash)
return harness
diff --git a/examples/headless-agent/tests/fixtures/goal-domain/cordis.yml b/examples/headless-agent/tests/fixtures/goal-domain/cordis.yml
index d20c05b785..d66713526e 100644
--- a/examples/headless-agent/tests/fixtures/goal-domain/cordis.yml
+++ b/examples/headless-agent/tests/fixtures/goal-domain/cordis.yml
@@ -3,8 +3,8 @@
name: '../cli-mock-llm.ts'
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/headless-agent/tests/fixtures/time-context.cordis.yml b/examples/headless-agent/tests/fixtures/time-context.cordis.yml
index 1050d32376..a105652e9c 100644
--- a/examples/headless-agent/tests/fixtures/time-context.cordis.yml
+++ b/examples/headless-agent/tests/fixtures/time-context.cordis.yml
@@ -3,8 +3,8 @@
name: './time-context-mock-llm.ts'
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/headless-agent/tests/harness.ts b/examples/headless-agent/tests/harness.ts
index cf5f30aaee..f0cde5c274 100644
--- a/examples/headless-agent/tests/harness.ts
+++ b/examples/headless-agent/tests/harness.ts
@@ -4,7 +4,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as ToolTodo from '@deepseek-ai/dsh-tool-todo'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
@@ -60,7 +60,7 @@ export async function codingHarness(workdir: string, options: CodingHarnessOptio
await ctx.plugin(LlmDeepSeek, options.modelContextWindow === undefined ? {} : {
models: [{ id: 'deepseek-v4-flash', contextWindow: options.modelContextWindow }],
})
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })
await ctx.plugin(ToolBash)
await ctx.plugin(ToolTodo)
diff --git a/examples/jsonrpc-agent/cordis.yml b/examples/jsonrpc-agent/cordis.yml
index 7576b06cbf..b1364679f9 100644
--- a/examples/jsonrpc-agent/cordis.yml
+++ b/examples/jsonrpc-agent/cordis.yml
@@ -18,8 +18,8 @@
reasoningEffort: max
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/package.json b/examples/package.json
index 183aa03111..237a46df28 100644
--- a/examples/package.json
+++ b/examples/package.json
@@ -31,7 +31,7 @@
"@deepseek-ai/dsh-lsp": "workspace:*",
"@deepseek-ai/dsh-lsp-local": "workspace:*",
"@deepseek-ai/dsh-plan-mode": "workspace:*",
- "@deepseek-ai/dsh-process-local": "workspace:*",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:*",
"@deepseek-ai/dsh-permission": "workspace:*",
"@deepseek-ai/dsh-pty": "workspace:*",
"@deepseek-ai/dsh-pty-local": "workspace:*",
diff --git a/examples/tui-agent/composition.md b/examples/tui-agent/composition.md
index 9c64a95fbb..c6fc223113 100644
--- a/examples/tui-agent/composition.md
+++ b/examples/tui-agent/composition.md
@@ -12,8 +12,8 @@ flowchart LR
cfg --> plugin_tui_hmr
plugin_tui_llm_deepseek["llm-deepseek
@deepseek-ai/dsh-llm-deepseek"]
cfg --> plugin_tui_llm_deepseek
- plugin_tui_processes["processes
@deepseek-ai/dsh-process-local"]
- cfg --> plugin_tui_processes
+ plugin_tui_subprocess["subprocess
@deepseek-ai/dsh-subprocess-local"]
+ cfg --> plugin_tui_subprocess
plugin_tui_bash["bash
@deepseek-ai/dsh-bash-local"]
cfg --> plugin_tui_bash
plugin_tui_tui_agent["tui-agent
@deepseek-ai/dsh-tui-demo"]
@@ -71,7 +71,7 @@ flowchart LR
| --- | --- |
| `hmr` | `@cordisjs/plugin-hmr` |
| `llm-deepseek` | `@deepseek-ai/dsh-llm-deepseek` |
-| `processes` | `@deepseek-ai/dsh-process-local` |
+| `subprocess` | `@deepseek-ai/dsh-subprocess-local` |
| `bash` | `@deepseek-ai/dsh-bash-local` |
| `tui-agent` | `@deepseek-ai/dsh-tui-demo` |
| `session-title-llm` | `@deepseek-ai/dsh-session-title-first-message-llm` |
diff --git a/examples/tui-agent/cordis.yml b/examples/tui-agent/cordis.yml
index 9a67ce4f77..7c8b03db05 100644
--- a/examples/tui-agent/cordis.yml
+++ b/examples/tui-agent/cordis.yml
@@ -22,8 +22,8 @@
# Local executor for the app bundle's bash tool.
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml b/examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml
index f25cafc64b..a32bdf987b 100644
--- a/examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml
+++ b/examples/tui-agent/tests/fixtures/tui-scripted.cordis.yml
@@ -5,8 +5,8 @@
name: './tui-scripted-llm.ts'
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/examples/tui-agent/tests/tui.snapshot.ts b/examples/tui-agent/tests/tui.snapshot.ts
index 795f21a794..8b99bc302b 100644
--- a/examples/tui-agent/tests/tui.snapshot.ts
+++ b/examples/tui-agent/tests/tui.snapshot.ts
@@ -8,7 +8,7 @@ import { scrubRequestHeaders } from '@deepseek-ai/dsh-acp-snapshot'
import type { Agent } from '@deepseek-ai/dsh-agent'
import * as AgentCore from '@deepseek-ai/dsh-agent-spine-demo'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import WorkerCodeRuntime from '@deepseek-ai/dsh-code-runtime-worker'
import CommandService from '@deepseek-ai/dsh-commands'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
@@ -205,7 +205,7 @@ async function mountScenarioContext(
skills: { local: { agentsHome: join(cwd, '.agents') } },
})
await ctx.plugin(TokenMeterService)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 })
await ctx.plugin(SnapshotLocalFileSystem, { cwd: '/' })
await ctx.plugin(FsPolicy)
diff --git a/packages/README.md b/packages/README.md
index f9707a178d..2c559981a7 100644
--- a/packages/README.md
+++ b/packages/README.md
@@ -11,7 +11,7 @@ Packages live at `packages///`; groups are containers, while names r
| [`core/`](core/README.md) | Product API spine: sessions, prompts, tools, agent services, and the concrete loop | Product — stable surface |
| [`goal/`](goal/README.md) | Persisted same-session goal state and lifecycle | Product — stable surface |
| [`llm/`](llm/README.md) | LLM capability family: the abstract service + provider adapters | Product — stable surface |
-| [`process/`](process/README.md) | Child-process manager capability family: spawn seam + local process-group implementation | Product — stable surface |
+| [`subprocess/`](subprocess/README.md) | Subprocess capability family: spawn seam + local process-group implementation | Product — stable surface |
| [`bash/`](bash/README.md) | Bash capability family: executor seam, local impl, model-facing tool | Product — stable surface |
| [`pty/`](pty/README.md) | Persistent PTY capability family: owner-scoped sessions, local implementation, and model-facing tools | Product — stable surface |
| [`code-runtime/`](code-runtime/README.md) | Code-execution capability family: the runtime seam for model-written programs + a worker-thread backend | Product — stable surface |
diff --git a/packages/bash/README.md b/packages/bash/README.md
index 57b0ffcf9e..b9ec625353 100644
--- a/packages/bash/README.md
+++ b/packages/bash/README.md
@@ -4,8 +4,8 @@ The canonical three-package capability seam (see [capability seams](../../.agent
| Package | Role | ctx key |
|---|---|---|
-| `bash/` | Abstract bash executor seam (interface + vocabulary; sandbox result facts carry the [`sandbox/`](../sandbox/README.md) seam's mode/enforcement vocabulary, and the managed-env/output vocabulary is re-exported from the [`process/`](../process/README.md) seam) | `ctx.bash` |
-| `bash-local/` | Local `BashExecutor` implementation over the [`process/`](../process/README.md) manager (command defaulting, deadlines, terminal env, background-read merge) | (registers `ctx.bash`) |
+| `bash/` | Abstract bash executor seam (interface + vocabulary; sandbox result facts carry the [`sandbox/`](../sandbox/README.md) seam's mode/enforcement vocabulary, and the managed-env/output vocabulary is re-exported from the [`subprocess/`](../subprocess/README.md) seam) | `ctx.bash` |
+| `bash-local/` | Local `BashExecutor` implementation over the [`subprocess/`](../subprocess/README.md) service (command defaulting, deadlines, terminal env, background-read merge) | (registers `ctx.bash`) |
| `bash-sandbox/` | Sandbox-consuming `BashExecutor` (wraps every command argv via `ctx.sandbox`, stamps denial/enforcement facts; extends `bash-local`'s mechanics) | (registers `ctx.bash`) |
| `tool-bash/` | Model-facing `bash` schema; background processes register with the generic [`tasks/`](../tasks/README.md) runtime | (registers on `ctx.tools`) |
diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md
index 5fb4bd0a39..259c2e4950 100644
--- a/packages/bash/bash-local/README.md
+++ b/packages/bash/bash-local/README.md
@@ -1,6 +1,6 @@
# @deepseek-ai/dsh-bash-local
-Local implementation of the `@deepseek-ai/dsh-bash` executor seam over the [`@deepseek-ai/dsh-process`](../../process/process/README.md) manager: `LocalBashExecutor` spawns `bash -c ` per call as a managed process group through `ctx.processes`, and owns everything bash-shaped — command defaulting and caps, timeout/cancel classification, the model-friendly terminal environment, and the model-facing stdout/stderr merge for background reads. Group mechanics (bounded spill-backed output, credential scrub, kill escalation, disposal) are the process manager's.
+Local implementation of the `@deepseek-ai/dsh-bash` executor seam over the [`@deepseek-ai/dsh-subprocess`](../../subprocess/subprocess/README.md) service: `LocalBashExecutor` spawns `bash -c ` per call as a managed process group through `ctx.subprocess`, and owns everything bash-shaped — command defaulting and caps, timeout/cancel classification, the model-friendly terminal environment, and the model-facing stdout/stderr merge for background reads. Group mechanics (bounded spill-backed output, credential scrub, kill escalation, disposal) are the subprocess service's.
The package root exports the default and named `LocalBashExecutor` plugin plus its `Config`.
@@ -23,10 +23,10 @@ The package root exports the default and named `LocalBashExecutor` plugin plus i
Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; the notable choices:
- **Spawn per call, no shell state** — every call is a fresh non-login `bash -c` (deterministic; no rc files). All four surveyed tools spawn per call. `XXX(stateful-shell)` in `src/index.ts` records the two proven stateful designs (Claude Code's cwd-only persistence; Codex's PTY exec sessions) for when real workflows demand them.
-- **Configured budgets over managed groups** — `resolve()` fills `workdir`/`timeoutMs`/`stdoutMaxBytes` from config, and every spawn hands the manager explicit byte caps, spill cap, and `graceMs` (default 3s — OpenCode's escalation). Process-group kills, the post-exit pipe-drain grace, tail-keep truncation, and bounded spill files are [`dsh-process-local`](../../process/process-local/README.md) mechanics. A foreground `BashExecRequest.stdoutMaxBytes` can raise stdout's capture budget for one trusted caller; stderr and background runs still use `maxOutputBytes`.
+- **Configured budgets over managed groups** — `resolve()` fills `workdir`/`timeoutMs`/`stdoutMaxBytes` from config, and every spawn hands the service explicit byte caps, spill cap, and `graceMs` (default 3s — OpenCode's escalation). Process-group kills, the post-exit pipe-drain grace, tail-keep truncation, and bounded spill files are [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md) mechanics. A foreground `BashExecRequest.stdoutMaxBytes` can raise stdout's capture budget for one trusted caller; stderr and background runs still use `maxOutputBytes`.
- **Timeout and cancel classification** — `run()` fuses its config-clamped timeout with the caller's signal through one deadline; only the executor's own timeout reports `timedOut`, an upstream cancel reports `aborted`, and a self-signaled command reports neither ([timeout-library Agent Note](../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)).
-- **Model-friendly terminal env** — `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results, merged as ordinary env under the manager's credential scrub and `DSH_*` channel rules; an explicit caller entry still wins. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
-- **Background processes** — `start()` returns a live `BashProcess` handle immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), and the handle's `readOutput()` merges the manager's offset-based stdout/stderr reads into one marked-section delta with a consuming cursor. A still-running process belongs to the manager, so it survives executor reloads and dies (killed and joined) with the manager's disposal. Everything task-shaped (ids, ownership, polling, notices) lives in the generic [`ctx.tasks` runtime](../../tasks/tasks/README.md), which the tool layer registers the handle with — this executor never sees a session or a registry.
+- **Model-friendly terminal env** — `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results, merged as ordinary env under the service's credential scrub and `DSH_*` channel rules; an explicit caller entry still wins. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
+- **Background processes** — `start()` returns a live `BashProcess` handle immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), and the handle's `readOutput()` merges the service's offset-based stdout/stderr reads into one marked-section delta with a consuming cursor. A still-running process belongs to the subprocess service, so it survives executor reloads and dies (killed and joined) with the service's disposal. Everything task-shaped (ids, ownership, polling, notices) lives in the generic [`ctx.tasks` runtime](../../tasks/tasks/README.md), which the tool layer registers the handle with — this executor never sees a session or a registry.
## Model Experience
@@ -40,7 +40,7 @@ No direct invalidation; the named consumer owns any request-prefix changes.
- **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`.
- **No persistent shell or PTY** — every call starts a fresh non-login `bash -c`; cwd-only persistence and interactive terminal sessions remain deferred until a real workflow requires them.
-- **POSIX-only** — the `bash` binary is hardcoded, and the underlying manager's group semantics are POSIX; Windows is unsupported.
-- **A background spawn-failure note is single-delivery** — the manager buffers no output for a process that never ran, so the executor injects `spawn failed: …` into exactly one `readOutput()` delta; a reader that discards that delta cannot recover it.
+- **POSIX-only** — the `bash` binary is hardcoded, and the underlying service's group semantics are POSIX; Windows is unsupported.
+- **A background spawn-failure note is single-delivery** — the subprocess service buffers no output for a process that never ran, so the executor injects `spawn failed: …` into exactly one `readOutput()` delta; a reader that discards that delta cannot recover it.
-Scrub-heuristic and spill-retention caveats live with [`dsh-process-local`](../../process/process-local/README.md), which owns those mechanics.
+Scrub-heuristic and spill-retention caveats live with [`dsh-subprocess-local`](../../subprocess/subprocess-local/README.md), which owns those mechanics.
diff --git a/packages/bash/bash-local/package.json b/packages/bash/bash-local/package.json
index edfcafabe5..bacc7ac92d 100644
--- a/packages/bash/bash-local/package.json
+++ b/packages/bash/bash-local/package.json
@@ -29,7 +29,7 @@
"peerDependencies": {
"@deepseek-ai/dsh-bash": "^0.0.1",
"@deepseek-ai/dsh-invariants": "^0.0.1",
- "@deepseek-ai/dsh-process": "^0.0.1",
+ "@deepseek-ai/dsh-subprocess": "^0.0.1",
"@deepseek-ai/dsh-timeout": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
@@ -39,8 +39,8 @@
"devDependencies": {
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
- "@deepseek-ai/dsh-process": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-timeout": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
diff --git a/packages/bash/bash-local/src/index.ts b/packages/bash/bash-local/src/index.ts
index 5cb99b3caa..463e7a96d4 100644
--- a/packages/bash/bash-local/src/index.ts
+++ b/packages/bash/bash-local/src/index.ts
@@ -1,7 +1,7 @@
/**
- * Local implementation of the bash executor seam over the process-manager
+ * Local implementation of the bash executor seam over the subprocess
* seam. Each command runs as `bash -c` in a managed process group spawned
- * through `ctx.processes`; this executor owns command defaulting, deadlines
+ * through `ctx.subprocess`; this executor owns command defaulting, deadlines
* and cause classification, the model-friendly terminal environment, and the
* model-facing stdout/stderr merge for background reads. Execution policy
* belongs in `tools/pre-execute` or a sandboxing executor.
@@ -12,7 +12,7 @@ import { Context } from 'cordis'
import z from 'schemastery'
import { BashExecutor } from '@deepseek-ai/dsh-bash'
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult } from '@deepseek-ai/dsh-bash'
-import type { ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
+import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
import { clampTimeout, deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
/**
@@ -20,7 +20,7 @@ import { clampTimeout, deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
* interactive terminal features that would garble tool output (the same set
* Codex hardcodes; Claude Code achieves it via TERM=dumb). Bash-tool policy —
* merged into the ordinary env channel, so a trusted caller's own entry still
- * wins; the process manager applies its credential scrub independently.
+ * wins; the subprocess service applies its credential scrub independently.
*/
export const ENV_OVERRIDES = {
NO_COLOR: '1',
@@ -61,14 +61,14 @@ function assertPositiveFinite(name: string, value: number): void {
}
/**
- * Local bash executor over `ctx.processes`. Bounded output, spill files, and
- * process-group SIGTERM→SIGKILL escalation are the process manager's
+ * Local bash executor over `ctx.subprocess`. Bounded output, spill files, and
+ * process-group SIGTERM→SIGKILL escalation are the subprocess service's
* mechanics; this executor supplies their configured budgets per spawn, so a
* still-running background process stays managed (killed and joined at
* composition teardown) even across an executor reload.
*/
export class LocalBashExecutor extends BashExecutor {
- static inject = ['processes']
+ static inject = ['subprocess']
static Config: z = z.object({
cwd: z.string(),
@@ -116,7 +116,7 @@ export class LocalBashExecutor extends BashExecutor {
stdoutMaxBytes,
...request.signal ? { signal: request.signal } : {},
// Carry stdin/ordinary env/trusted dshEnv through verbatim — optional,
- // no config default. The process manager owns the scrub and merge order.
+ // no config default. The subprocess service owns the scrub and merge order.
...request.stdin !== undefined ? { stdin: request.stdin } : {},
...request.env !== undefined ? { env: request.env } : {},
...request.dshEnv !== undefined ? { dshEnv: request.dshEnv } : {},
@@ -129,7 +129,7 @@ export class LocalBashExecutor extends BashExecutor {
/** Map one resolved bash spec onto a fully-specified process spawn. */
// XXX(stateful-shell): evaluate persistent cwd or PTY sessions when workflows require shell state.
- private spawnSpec(spec: BashExecSpec, stdoutMaxBytes: number, signal: AbortSignal | undefined): ProcessSpawnSpec {
+ private spawnSpec(spec: BashExecSpec, stdoutMaxBytes: number, signal: AbortSignal | undefined): SubprocessSpawnSpec {
return {
argv: ['bash', '-c', spec.command],
cwd: spec.workdir,
@@ -147,7 +147,7 @@ export class LocalBashExecutor extends BashExecutor {
async run(spec: BashExecSpec): Promise {
// One deadline combines timeout and upstream cancellation; disposal clears its timer.
using d = deadline(spec.signal, spec.timeoutMs, 'BASH_TIMEOUT')
- const outcome = await this.ctx.processes.spawn(this.spawnSpec(spec, spec.stdoutMaxBytes, d.signal)).done
+ const outcome = await this.ctx.subprocess.spawn(this.spawnSpec(spec, spec.stdoutMaxBytes, d.signal)).done
// Only this executor's timeout reason counts as timedOut; outer deadlines count as aborts.
const timedOut = timeoutOf(d.signal, 'BASH_TIMEOUT') !== undefined
const aborted = d.signal.aborted && !timedOut
@@ -156,9 +156,9 @@ export class LocalBashExecutor extends BashExecutor {
start(spec: BashExecSpec): BashProcess {
// Background runs ignore timeoutMs; callers stop them through kill() or spec.signal.
- const running = this.ctx.processes.spawn(this.spawnSpec(spec, this.config.maxOutputBytes, spec.signal))
+ const running = this.ctx.subprocess.spawn(this.spawnSpec(spec, this.config.maxOutputBytes, spec.signal))
- // A spawn failure produces no process output, so the manager has nothing
+ // A spawn failure produces no process output, so the subprocess service has nothing
// to buffer; the note is delivered exactly once through the read path.
let spawnFailureNote: string | undefined
const consumeSpawnFailure = (): string => {
diff --git a/packages/bash/bash-local/tests/executor.spec.ts b/packages/bash/bash-local/tests/executor.spec.ts
index aa5eb2fb92..d174b264ab 100644
--- a/packages/bash/bash-local/tests/executor.spec.ts
+++ b/packages/bash/bash-local/tests/executor.spec.ts
@@ -4,15 +4,15 @@ import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import type { BashProcess } from '@deepseek-ai/dsh-bash'
const spillDir = mkdtempSync(join(tmpdir(), 'dsh-bash-exec-spec-'))
async function setup(config: ConstructorParameters[1] = {}) {
const ctx = new Context()
- await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
// A short kill grace via the REAL config path, so escalation tests stay fast.
await ctx.plugin(LocalBashExecutor, { graceMs: 200, ...config })
const bash = ctx.bash as LocalBashExecutor
@@ -295,11 +295,11 @@ describe('LocalBashExecutor.start (background process handles)', () => {
})
})
-describe('process lifecycle ownership (the manager, not the executor)', () => {
- it('a background process survives executor-fiber disposal and dies with the process manager', async () => {
+describe('process lifecycle ownership (the subprocess service, not the executor)', () => {
+ it('a background process survives executor-fiber disposal and dies with the subprocess service', async () => {
const ctx = new Context()
- const managerFiber = await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ const managerFiber = await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
const executorFiber = await ctx.plugin(LocalBashExecutor, { graceMs: 200 })
const bash = ctx.bash as LocalBashExecutor
@@ -316,17 +316,17 @@ describe('process lifecycle ownership (the manager, not the executor)', () => {
expect(proc.status).toBe('running')
expect(() => process.kill(pid, 0)).not.toThrow()
- // Manager disposal kills the group and AWAITS its exit (no orphans).
+ // Service disposal kills the group and AWAITS its exit (no orphans).
await managerFiber.dispose()
expect(() => process.kill(pid, 0)).toThrow()
await proc.done
expect(proc.status).toBe('killed')
})
- it('manager disposal escalates to SIGKILL for TERM-trapping children and settles handles', async () => {
+ it('service disposal escalates to SIGKILL for TERM-trapping children and settles handles', async () => {
const ctx = new Context()
- const managerFiber = await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ const managerFiber = await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
await ctx.plugin(LocalBashExecutor, { graceMs: 200 })
const bash = ctx.bash as LocalBashExecutor
diff --git a/packages/bash/bash-local/tsconfig.json b/packages/bash/bash-local/tsconfig.json
index ce24865d49..53ccc94926 100644
--- a/packages/bash/bash-local/tsconfig.json
+++ b/packages/bash/bash-local/tsconfig.json
@@ -27,7 +27,7 @@
"path": "../../bash/bash"
},
{
- "path": "../../process/process"
+ "path": "../../subprocess/subprocess"
},
{
"path": "../../support/invariants"
diff --git a/packages/bash/bash-sandbox/package.json b/packages/bash/bash-sandbox/package.json
index b8737e0e68..0f2240630c 100644
--- a/packages/bash/bash-sandbox/package.json
+++ b/packages/bash/bash-sandbox/package.json
@@ -38,7 +38,7 @@
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
diff --git a/packages/bash/bash-sandbox/src/index.ts b/packages/bash/bash-sandbox/src/index.ts
index 3f0fc0c55d..3945809fa9 100644
--- a/packages/bash/bash-sandbox/src/index.ts
+++ b/packages/bash/bash-sandbox/src/index.ts
@@ -34,7 +34,7 @@ export type Config = LocalConfig
* mode; `result.sandbox` reports the mode and enforcement actually used.
*/
export class SandboxBashExecutor extends LocalBashExecutor {
- static override inject = ['processes', 'sandbox', 'sandboxPolicy']
+ static override inject = ['subprocess', 'sandbox', 'sandboxPolicy']
// No own Config: the sandbox default (mode + workspaceRoot) moved to
// ctx.sandboxPolicy, so this executor inherits LocalBashExecutor's Config
@@ -128,7 +128,7 @@ export class SandboxBashExecutor extends LocalBashExecutor {
* Wrap one shell command via the `ctx.sandbox` provider: hand over the
* exact `['bash', '-c', command]` argv this executor would spawn, get back
* the confined argv, and re-assemble it into the `exec …` command string
- * the inherited spawn path runs (the outer `bash -c` the process manager spawns
+ * the inherited spawn path runs (the outer `bash -c` the subprocess service spawns
* `exec`s into the runner, so no extra shell lingers). Provider errors
* (fail-closed `SANDBOX_UNAVAILABLE`) propagate to the caller unchanged.
*/
diff --git a/packages/bash/bash-sandbox/tests/bwrap.e2e.ts b/packages/bash/bash-sandbox/tests/bwrap.e2e.ts
index ad6ecab6f1..437078440c 100644
--- a/packages/bash/bash-sandbox/tests/bwrap.e2e.ts
+++ b/packages/bash/bash-sandbox/tests/bwrap.e2e.ts
@@ -9,7 +9,7 @@ import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
import { bwrapProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
/**
* Keyless integration of the real provider and executor through public run/start paths. With
@@ -43,7 +43,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w
ctx = new Context()
await ctx.plugin(LocalSandboxProvider, {})
await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 })
return ctx.bash as SandboxBashExecutor
}
diff --git a/packages/bash/bash-sandbox/tests/landlock.e2e.ts b/packages/bash/bash-sandbox/tests/landlock.e2e.ts
index aac9b766e8..0c5cfbe563 100644
--- a/packages/bash/bash-sandbox/tests/landlock.e2e.ts
+++ b/packages/bash/bash-sandbox/tests/landlock.e2e.ts
@@ -9,7 +9,7 @@ import { launcherPath } from 'node-addon-landlock-run'
import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
/**
* KEYLESS consumer-integration proof: the REAL `LocalSandboxProvider` (bwrap
@@ -48,7 +48,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w
await ctx.plugin(LocalSandboxProvider, {})
;(ctx.sandbox as LocalSandboxProvider).internals = { probeBwrap: () => false }
await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 })
return ctx.bash as SandboxBashExecutor
}
diff --git a/packages/bash/bash-sandbox/tests/sandbox.spec.ts b/packages/bash/bash-sandbox/tests/sandbox.spec.ts
index 3510c71737..90a67999c8 100644
--- a/packages/bash/bash-sandbox/tests/sandbox.spec.ts
+++ b/packages/bash/bash-sandbox/tests/sandbox.spec.ts
@@ -15,7 +15,7 @@ import { SANDBOX_UNAVAILABLE, SandboxProvider, SandboxUnavailableError } from '@
import type { ConfinedArgv, SandboxExecutionPolicy, SandboxMode, SandboxPolicy } from '@deepseek-ai/dsh-sandbox'
import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import { classifyDenial, classifyRunnerFailure, shellQuote } from '../src/helpers.ts'
import type { Config } from '@deepseek-ai/dsh-bash-sandbox'
@@ -59,8 +59,8 @@ async function setup(
...mode !== undefined ? { mode } : {},
...workspaceRoot !== undefined ? { workspaceRoot } : {},
})
- await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
await ctx.plugin(SandboxBashExecutor, { graceMs: 200, ...execConfig })
const bash = ctx.bash as SandboxBashExecutor
return { ctx, bash, calls }
diff --git a/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts b/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts
index 87e76d95e1..62b1569ee7 100644
--- a/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts
+++ b/packages/bash/bash-sandbox/tests/seatbelt.e2e.ts
@@ -9,7 +9,7 @@ import { LocalSandboxProvider } from '@deepseek-ai/dsh-sandbox-local'
import { SandboxPolicyService } from '@deepseek-ai/dsh-sandbox-policy'
import { seatbeltProfileArgs } from '@deepseek-ai/dsh-sandbox-local/src/profiles.ts'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
/**
* Keyless macOS integration of the real provider and executor through public run/start paths.
@@ -42,7 +42,7 @@ async function sandboxedBash(workspace: string, mode: 'read-only' | 'workspace-w
await ctx.plugin(LocalSandboxProvider, {})
;(ctx.sandbox as LocalSandboxProvider).internals = { probeBwrap: () => false, probeLandlock: () => 'unusable' }
await ctx.plugin(SandboxPolicyService, { mode, workspaceRoot: workspace })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(SandboxBashExecutor, { cwd: workspace, timeoutMs: 30_000 })
return ctx.bash as SandboxBashExecutor
}
diff --git a/packages/bash/bash/package.json b/packages/bash/bash/package.json
index 13c31ee2de..b8ff310f01 100644
--- a/packages/bash/bash/package.json
+++ b/packages/bash/bash/package.json
@@ -28,13 +28,13 @@
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
- "@deepseek-ai/dsh-process": "^0.0.1",
+ "@deepseek-ai/dsh-subprocess": "^0.0.1",
"@deepseek-ai/dsh-sandbox": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
- "@deepseek-ai/dsh-process": "workspace:^",
+ "@deepseek-ai/dsh-subprocess": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
diff --git a/packages/bash/bash/src/index.ts b/packages/bash/bash/src/index.ts
index ac03e73a91..4f8ae112a9 100644
--- a/packages/bash/bash/src/index.ts
+++ b/packages/bash/bash/src/index.ts
@@ -44,8 +44,8 @@ declare module 'cordis' {
* - {@link BashProcess.readOutput} is incremental: consecutive reads never
* repeat output. Lossy reads report truncation and available spill files.
* - A still-running background process is stopped and awaited when its
- * owning composition tears down. With the process-manager seam that
- * boundary is `ctx.processes` disposal, so a background process survives
+ * owning composition tears down. With the subprocess seam that
+ * boundary is `ctx.subprocess` disposal, so a background process survives
* an executor-only reload.
*/
export abstract class BashExecutor extends Service {
diff --git a/packages/bash/bash/src/types.ts b/packages/bash/bash/src/types.ts
index 4fdb11288e..ea153605af 100644
--- a/packages/bash/bash/src/types.ts
+++ b/packages/bash/bash/src/types.ts
@@ -2,16 +2,16 @@
* Execution types for the bash executor seam. Background task semantics belong
* to `@deepseek-ai/dsh-tasks`; this seam exposes only process handles. The
* managed-environment and captured-output vocabulary is owned by the
- * process-manager seam and re-exported here so bash consumers keep one import
+ * subprocess seam and re-exported here so bash consumers keep one import
* root.
* @module dsh-bash/types
*/
import type { SandboxEnforcement, SandboxExecutionPolicy, SandboxMode } from '@deepseek-ai/dsh-sandbox'
-import type { CollectedOutput, DshEnvironment } from '@deepseek-ai/dsh-process'
+import type { CollectedOutput, DshEnvironment } from '@deepseek-ai/dsh-subprocess'
-export { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-process'
-export type { CollectedOutput, DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-process'
+export { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-subprocess'
+export type { CollectedOutput, DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-subprocess'
/**
* Sandbox facts for one run, present iff a sandboxing executor handled it.
@@ -154,7 +154,7 @@ export interface BashProcessRead {
/**
* A background process handle returned by {@link BashExecutor.start}. It is the
* only access path; buffered output remains readable after exit. Composition
- * teardown (the process manager's disposal) kills running processes and
+ * teardown (the subprocess service's disposal) kills running processes and
* awaits {@link done}; an executor-only reload leaves them running.
*/
export interface BashProcess {
diff --git a/packages/bash/bash/tsconfig.json b/packages/bash/bash/tsconfig.json
index ad38e8e3e1..3f611c80e0 100644
--- a/packages/bash/bash/tsconfig.json
+++ b/packages/bash/bash/tsconfig.json
@@ -15,7 +15,7 @@
"path": "../../../vendor/cordis"
},
{
- "path": "../../process/process"
+ "path": "../../subprocess/subprocess"
},
{
"path": "../../sandbox/sandbox"
diff --git a/packages/bash/tool-bash/package.json b/packages/bash/tool-bash/package.json
index 821e9706c9..c34e1c6e7b 100644
--- a/packages/bash/tool-bash/package.json
+++ b/packages/bash/tool-bash/package.json
@@ -52,7 +52,7 @@
"@deepseek-ai/dsh-bash-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
diff --git a/packages/bash/tool-bash/tests/integration.spec.ts b/packages/bash/tool-bash/tests/integration.spec.ts
index 1a1d8bb4db..434f53ca2a 100644
--- a/packages/bash/tool-bash/tests/integration.spec.ts
+++ b/packages/bash/tool-bash/tests/integration.spec.ts
@@ -11,7 +11,7 @@ import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-test
import LocalTaskService from '@deepseek-ai/dsh-tasks-local'
import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -30,7 +30,7 @@ async function harness(adapter: MockAdapter, sessionRoot?: string, dshHome?: str
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(LocalTaskService)
await ctx.plugin(ToolTasks)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
await ctx.plugin(ToolBash, dshHome === undefined ? {} : { dshHome })
ctx.llm.registerAdapter(['mock'], adapter)
diff --git a/packages/bash/tool-bash/tests/tools.spec.ts b/packages/bash/tool-bash/tests/tools.spec.ts
index 4b7647c519..15222ee647 100644
--- a/packages/bash/tool-bash/tests/tools.spec.ts
+++ b/packages/bash/tool-bash/tests/tools.spec.ts
@@ -17,7 +17,7 @@ import * as ToolTasks from '@deepseek-ai/dsh-tool-tasks'
import ApprovalService from '@deepseek-ai/dsh-user-approval'
import type { ApprovalOutcome } from '@deepseek-ai/dsh-user-approval'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import SandboxPolicyService from '@deepseek-ai/dsh-sandbox-policy'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import { processOutcome } from '../src/background.ts'
@@ -33,8 +33,8 @@ async function setup() {
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
await ctx.plugin(AgentRegistry)
- await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, graceMs: 200 })
await ctx.plugin(ToolBash)
return ctx
@@ -48,8 +48,8 @@ async function setupWithTasks() {
await ctx.plugin(AgentRegistry)
await ctx.plugin(LocalTaskService)
await ctx.plugin(ToolTasks)
- await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, graceMs: 200 })
await ctx.plugin(ToolBash)
return ctx
@@ -278,8 +278,8 @@ describe('bash tool', () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
- await ctx.plugin(LocalProcessManager)
- ;(ctx.processes as LocalProcessManager).internals = { spillDir }
+ await ctx.plugin(LocalSubprocessService)
+ ;(ctx.subprocess as LocalSubprocessService).internals = { spillDir }
await ctx.plugin(LocalBashExecutor, { maxOutputBytes: 100, graceMs: 200 })
await ctx.plugin(ToolBash)
const result = await call(ctx, 'bash', { command: 'for i in $(seq 1 100); do printf "line-%04d\\n" $i; done', description: 'test command' })
@@ -387,7 +387,7 @@ describe('bash tool', () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, {})
const fiber = await ctx.plugin(ToolBash)
expect(ctx.tools.schemas()).toHaveLength(1)
@@ -405,7 +405,7 @@ describe('bash tool', () => {
// inject: ['tools', 'bash'] keeps the plugin pending until bash exists.
await ctx.plugin(ToolBash)
expect(ctx.tools.schemas()).toHaveLength(0)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, {})
await new Promise(resolve => setTimeout(resolve, 0))
expect(ctx.tools.schemas()).toHaveLength(1)
@@ -417,7 +417,7 @@ describe('bash tool', () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, {})
ToolBash.apply(ctx, {})
const schema = ctx.tools.schemas()[0]!
@@ -533,7 +533,7 @@ describe('background execution through the task runtime', () => {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, {})
await ctx.plugin(ToolBash, { enableRunInBackground: false })
diff --git a/packages/cordis/tool-cordis/src/api-catalog.ts b/packages/cordis/tool-cordis/src/api-catalog.ts
index a15c306e2f..59b97aedd5 100644
--- a/packages/cordis/tool-cordis/src/api-catalog.ts
+++ b/packages/cordis/tool-cordis/src/api-catalog.ts
@@ -426,16 +426,6 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
],
},
- {
- key: 'processes',
- summary: 'Abstract process manager.',
- methods: [
- {
- signature: 'abstract spawn(spec: ProcessSpawnSpec): ProcessHandle',
- jsDoc: '/**\n * Start one managed child process from a fully-specified spec; this seam\n * applies no defaults.\n * @param spec - argv, directory, limits, grace, cancellation, and environment.\n * @returns the live process handle (readers, kill, outcome promise).\n */',
- },
- ],
- },
{
key: 'pty',
summary: 'In-process registry for replaceable PTY backends and exact-Agent sessions.',
@@ -754,6 +744,16 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
],
},
+ {
+ key: 'subprocess',
+ summary: 'Abstract subprocess service.',
+ methods: [
+ {
+ signature: 'abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle',
+ jsDoc: '/**\n * Start one managed child process from a fully-specified spec; this seam\n * applies no defaults.\n * @param spec - argv, directory, limits, grace, cancellation, and environment.\n * @returns the live process handle (readers, kill, outcome promise).\n */',
+ },
+ ],
+ },
{
key: 'systemPrompt',
summary: 'Registry service for the prompt inputs assembled before each model step.',
@@ -1458,10 +1458,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'CodeRunResult',
declaration: 'export interface CodeRunResult {\n value?: CodeJsonValue;\n logs: string[];\n error?: CodeRunFailure;\n}',
},
- {
- name: 'CollectedOutput',
- declaration: 'export interface CollectedOutput {\n text: string;\n truncated: boolean;\n spillPath?: string;\n}',
- },
{
name: 'CommandDefinition',
declaration: 'export interface CommandDefinition {\n readonly name: string;\n readonly description: string;\n readonly input?: CommandInputDescriptor;\n readonly handler: (invocation: CommandInvocation) => CommandResult | Promise;\n}',
@@ -1558,14 +1554,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'DomainTableSpec',
declaration: 'export interface DomainTableSpec {\n readonly valueSchema: ZodType;\n readonly __key?: K;\n}',
},
- {
- name: 'DshEnvironment',
- declaration: 'export type DshEnvironment = Readonly>;',
- },
- {
- name: 'DshEnvironmentKey',
- declaration: 'export type DshEnvironmentKey = `${typeof DSH_ENV_PREFIX}${string}`;',
- },
{
name: 'EditGoalRequest',
declaration: 'export interface EditGoalRequest {\n readonly objective?: string;\n readonly maxGoalRounds?: number;\n}',
@@ -1770,26 +1758,6 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'PresetSpec',
declaration: 'export interface PresetSpec {\n sandbox: SandboxMode;\n approval: ApprovalPolicy;\n name?: string;\n description?: string;\n}',
},
- {
- name: 'ProcessHandle',
- declaration: 'export interface ProcessHandle {\n readonly pid: number;\n readonly stdout: ProcessOutputReader;\n readonly stderr: ProcessOutputReader;\n readonly done: Promise;\n kill(): void;\n}',
- },
- {
- name: 'ProcessOutcome',
- declaration: 'export interface ProcessOutcome {\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n stdout: CollectedOutput;\n stderr: CollectedOutput;\n}',
- },
- {
- name: 'ProcessOutputRead',
- declaration: 'export interface ProcessOutputRead {\n text: string;\n nextOffset: number;\n lossy: boolean;\n spillPath?: string;\n}',
- },
- {
- name: 'ProcessOutputReader',
- declaration: 'export interface ProcessOutputReader {\n readFrom(fromByte: number): ProcessOutputRead;\n}',
- },
- {
- name: 'ProcessSpawnSpec',
- declaration: 'export interface ProcessSpawnSpec {\n argv: readonly string[];\n cwd: string;\n stdoutMaxBytes: number;\n stderrMaxBytes: number;\n maxSpillBytes: number;\n graceMs: number;\n signal?: AbortSignal | undefined;\n stdin?: string | undefined;\n env?: Record | undefined;\n dshEnv?: DshEnvironment | undefined;\n}',
- },
{
name: 'PromptAssembly',
declaration: 'export interface PromptAssembly {\n sections: AssembledSection[];\n tools: ToolSchema[];\n variables: Record;\n}',
diff --git a/packages/examples/acp-demo/tests/built-bin.e2e.ts b/packages/examples/acp-demo/tests/built-bin.e2e.ts
index 464196433e..a82896ab73 100644
--- a/packages/examples/acp-demo/tests/built-bin.e2e.ts
+++ b/packages/examples/acp-demo/tests/built-bin.e2e.ts
@@ -94,8 +94,8 @@ async function makeConsumer(): Promise {
await writeFile(join(dir, 'cordis.yml'), [
'- id: mock-llm',
' name: \'./mock-llm.mjs\'',
- '- id: processes',
- ' name: \'@deepseek-ai/dsh-process-local\'',
+ '- id: subprocess',
+ ' name: \'@deepseek-ai/dsh-subprocess-local\'',
'- id: bash',
' name: \'@deepseek-ai/dsh-bash-local\'',
'- id: acp-agent',
diff --git a/packages/examples/acp-demo/tests/load-path.e2e.ts b/packages/examples/acp-demo/tests/load-path.e2e.ts
index 624db3d97a..8bc14c7330 100644
--- a/packages/examples/acp-demo/tests/load-path.e2e.ts
+++ b/packages/examples/acp-demo/tests/load-path.e2e.ts
@@ -35,8 +35,8 @@ const CORDIS_YML = `
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
- id: acp-agent
diff --git a/packages/examples/agent-spine-demo/package.json b/packages/examples/agent-spine-demo/package.json
index 6ecca27aed..e76db97daf 100644
--- a/packages/examples/agent-spine-demo/package.json
+++ b/packages/examples/agent-spine-demo/package.json
@@ -65,7 +65,7 @@
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-llm-retry": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-local": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^",
diff --git a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts
index f22947eb3b..46f3f56dc3 100644
--- a/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts
+++ b/packages/examples/agent-spine-demo/tests/multi-project-sandbox.e2e.ts
@@ -5,7 +5,7 @@ import { basename, join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { SandboxBashExecutor } from '@deepseek-ai/dsh-bash-sandbox'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as FsPolicy from '@deepseek-ai/dsh-fs-policy'
import SandboxedFileSystem from '@deepseek-ai/dsh-fs-sandbox'
import { CallId } from '@deepseek-ai/dsh-llm'
@@ -53,7 +53,7 @@ beforeEach(async () => {
ctx = new Context()
await ctx.plugin(LocalSandboxProvider, {})
await ctx.plugin(SandboxPolicyService, { mode: 'workspace-write', workspaceRoot: fallbackRoot })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(SandboxBashExecutor, { cwd: fallbackRoot, timeoutMs: 30_000 })
await ctx.plugin(SandboxedFileSystem, { cwd: fallbackRoot })
await ctx.plugin(agentSpine, {
diff --git a/packages/examples/cli-demo/tests/built-bin.e2e.ts b/packages/examples/cli-demo/tests/built-bin.e2e.ts
index 25169948e0..6f80c983f3 100644
--- a/packages/examples/cli-demo/tests/built-bin.e2e.ts
+++ b/packages/examples/cli-demo/tests/built-bin.e2e.ts
@@ -80,8 +80,8 @@ async function makeConsumer(): Promise {
await writeFile(join(dir, 'cordis.yml'), [
'- id: mock-llm',
" name: './mock-llm.ts'",
- '- id: processes',
- " name: '@deepseek-ai/dsh-process-local'",
+ '- id: subprocess',
+ " name: '@deepseek-ai/dsh-subprocess-local'",
'- id: bash',
" name: '@deepseek-ai/dsh-bash-local'",
'- id: cli-agent',
diff --git a/packages/fs/tool-fs-search/package.json b/packages/fs/tool-fs-search/package.json
index c0abc66a50..bf9cf15aa0 100644
--- a/packages/fs/tool-fs-search/package.json
+++ b/packages/fs/tool-fs-search/package.json
@@ -44,7 +44,7 @@
"@deepseek-ai/dsh-agent": "workspace:^",
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-retention": "workspace:^",
diff --git a/packages/fs/tool-fs-search/tests/integration.spec.ts b/packages/fs/tool-fs-search/tests/integration.spec.ts
index 3ba8e86c36..6be9629460 100644
--- a/packages/fs/tool-fs-search/tests/integration.spec.ts
+++ b/packages/fs/tool-fs-search/tests/integration.spec.ts
@@ -18,7 +18,7 @@ import { CallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as ToolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
const testToolSignal = new AbortController().signal
@@ -62,7 +62,7 @@ describe.skipIf(!hasRg)('search tools over the real bash executor + real rg', ()
ctx = new Context()
await ctx.plugin(SystemPrompt)
await ctx.plugin(ToolRegistry)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { cwd: dir, timeoutMs: 20_000 })
await ctx.plugin(ToolFsSearch)
})
diff --git a/packages/hooks/hooks-claude/package.json b/packages/hooks/hooks-claude/package.json
index bf333718fa..4d52d1f0c5 100644
--- a/packages/hooks/hooks-claude/package.json
+++ b/packages/hooks/hooks-claude/package.json
@@ -46,7 +46,7 @@
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-hook-protocol": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
diff --git a/packages/hooks/hooks-claude/tests/bridge.spec.ts b/packages/hooks/hooks-claude/tests/bridge.spec.ts
index 3cf70d569a..1bceaa3529 100644
--- a/packages/hooks/hooks-claude/tests/bridge.spec.ts
+++ b/packages/hooks/hooks-claude/tests/bridge.spec.ts
@@ -10,7 +10,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import { scopeTarget } from '@deepseek-ai/dsh-scope'
import SubagentService, { SubagentRunId } from '@deepseek-ai/dsh-subagent'
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
@@ -53,7 +53,7 @@ async function harnessWithFiber(configDir: string, adapter: MockAdapter): Promis
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
const hooks = await ctx.plugin(HooksClaude, { configPath: join(configDir, 'hooks.json') })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -355,7 +355,7 @@ describe('hooks-claude bridge — load resilience', () => {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
await ctx.plugin(HooksClaude, { configPath: '/nonexistent/hooks.json' })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -377,7 +377,7 @@ describe('hooks-claude bridge — load resilience', () => {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
const fiber = await ctx.plugin(HooksClaude, { configPath: join(dir, 'hooks.json') })
await fiber.dispose()
diff --git a/packages/hooks/hooks-claude/tests/coverage-cases.ts b/packages/hooks/hooks-claude/tests/coverage-cases.ts
index 1470c5f9a9..a773a1a74d 100644
--- a/packages/hooks/hooks-claude/tests/coverage-cases.ts
+++ b/packages/hooks/hooks-claude/tests/coverage-cases.ts
@@ -10,7 +10,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import { scopeTarget } from '@deepseek-ai/dsh-scope'
import SubagentService, { SubagentRunId } from '@deepseek-ai/dsh-subagent'
import * as HooksClaude from '@deepseek-ai/dsh-hooks-claude'
@@ -42,7 +42,7 @@ async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOp
await mountAgentLoopTestDependencies(ctx)
if (opts.sessionRoot !== undefined) await ctx.plugin(SessionPersistenceJsonl, { root: opts.sessionRoot })
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
await ctx.plugin(HooksClaude, { configPath, ...opts })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -361,7 +361,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
// Direct apply with only configPath — bypasses schemastery's defaults, so
// the bridge must run on the raw minimal config (the per-hook timeout is
@@ -660,7 +660,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
// Executor default cwd = serverDir (deliberately NOT the session cwd).
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
await ctx.plugin(HooksClaude, { configPath: join(serverDir, 'hooks.json') })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -690,7 +690,7 @@ export function defineCoverageCases(group: CoverageGroup): void {
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
// Executor default cwd = serverDir (deliberately NOT the child session cwd).
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
await ctx.plugin(HooksClaude, { configPath: join(serverDir, 'hooks.json') })
ctx.llm.registerAdapter(['mock'], new MockAdapter([]))
diff --git a/packages/hooks/hooks-codex/package.json b/packages/hooks/hooks-codex/package.json
index 472e010a93..fb598b4da3 100644
--- a/packages/hooks/hooks-codex/package.json
+++ b/packages/hooks/hooks-codex/package.json
@@ -45,7 +45,7 @@
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-bash": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-hook-protocol": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
diff --git a/packages/hooks/hooks-codex/tests/bridge.spec.ts b/packages/hooks/hooks-codex/tests/bridge.spec.ts
index eb34a5c1f8..3e1e3f8f93 100644
--- a/packages/hooks/hooks-codex/tests/bridge.spec.ts
+++ b/packages/hooks/hooks-codex/tests/bridge.spec.ts
@@ -10,7 +10,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -42,7 +42,7 @@ async function harness(dir: string, adapter: MockAdapter): Promise {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'test-model' })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -166,7 +166,7 @@ describe('hooks-codex bridge', () => {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
const fiber = await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'm' })
await fiber.dispose()
@@ -189,7 +189,7 @@ describe('hooks-codex bridge', () => {
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
const fiber = await ctx.plugin(HooksCodex, { configPath: join(dir, 'hooks.json'), model: 'm' })
ctx.llm.registerAdapter(['mock'], new MockAdapter([]))
diff --git a/packages/hooks/hooks-codex/tests/coverage-cases.ts b/packages/hooks/hooks-codex/tests/coverage-cases.ts
index 4bfaf4f98a..1c3c8edff4 100644
--- a/packages/hooks/hooks-codex/tests/coverage-cases.ts
+++ b/packages/hooks/hooks-codex/tests/coverage-cases.ts
@@ -10,7 +10,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex'
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
@@ -32,7 +32,7 @@ async function harness(configPath: string, adapter: MockAdapter, opts: HarnessOp
await mountAgentLoopTestDependencies(ctx)
if (opts.sessionRoot !== undefined) await ctx.plugin(SessionPersistenceJsonl, { root: opts.sessionRoot })
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
await ctx.plugin(HooksCodex, { configPath, model: 'm', ...opts })
ctx.llm.registerAdapter(['mock'], adapter)
@@ -312,7 +312,7 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000 })
ctx.logger.warn = warn as never
// Direct apply (schema bypass) → the `model ?? ''` fallback is exercised.
@@ -622,7 +622,7 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro
const ctx = new Context()
await mountAgentLoopTestDependencies(ctx)
await ctx.plugin(AgentLoop, { agents: [] })
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { timeoutMs: 10_000, cwd: serverDir })
await ctx.plugin(HooksCodex, { configPath: join(serverDir, 'hooks.json'), model: 'm' })
ctx.llm.registerAdapter(['mock'], adapter)
diff --git a/packages/process/README.md b/packages/process/README.md
deleted file mode 100644
index 956f08e3fe..0000000000
--- a/packages/process/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# process/ — child-process manager capability family
-
-The shared home for spawning managed child-process groups: fully-specified spawn specs, bounded tail-keep output with spill files, credential-scrubbed environments, offset-based incremental reads, and SIGTERM→grace→SIGKILL group kills. Command defaulting, shell semantics, deadlines, and presentation stay with consumers — the [bash executor family](../bash/README.md) is the first and owning consumer. See the [process-manager seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md).
-
-| Package | ctx key | Role |
-|---|---|---|
-| [`process`](process/README.md) (`@deepseek-ai/dsh-process`) | `ctx.processes` | The seam: abstract `ProcessManager.spawn(spec)`, the fully-explicit `ProcessSpawnSpec`, `ProcessHandle` with offset-based readers, and the shared `DSH_*` managed-environment and `CollectedOutput` vocabulary |
-| [`process-local`](process-local/README.md) (`@deepseek-ai/dsh-process-local`) | — | The local implementation: detached process groups, tail-keep truncation with bounded private spill files, the credential scrub and `DSH_*` merge order, kill escalation, and kill-and-join disposal |
-
-The manager owns process lifetime across consumer reloads; consumers own what a process means (a bash command, a future non-shell runner) and every default that shapes one.
diff --git a/packages/sdk/helper/src/features/builtin/index.ts b/packages/sdk/helper/src/features/builtin/index.ts
index a6873b75dc..27ba0aa581 100644
--- a/packages/sdk/helper/src/features/builtin/index.ts
+++ b/packages/sdk/helper/src/features/builtin/index.ts
@@ -33,7 +33,7 @@ export function createBuiltinRegistry(profile: ProjectProfile): FeatureRegistry
mode: 'exclusive',
required: true,
baseResources: [
- { kind: 'npm-cordis-config-entry', id: 'processes', package: '@deepseek-ai/dsh-process-local' },
+ { kind: 'npm-cordis-config-entry', id: 'subprocess', package: '@deepseek-ai/dsh-subprocess-local' },
{ kind: 'npm-cordis-config-entry', id: 'tool-bash', package: '@deepseek-ai/dsh-tool-bash' },
],
options: [
diff --git a/packages/subagent/subagent-inprocess/src/invariant.ts b/packages/subagent/subagent-inprocess/src/invariant.ts
index 7b8bfc36e2..4a2188dcc8 100644
--- a/packages/subagent/subagent-inprocess/src/invariant.ts
+++ b/packages/subagent/subagent-inprocess/src/invariant.ts
@@ -10,7 +10,7 @@ import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-inprocess'
/** Cordis companion plugin name. */
-export const name = 'subagent-inprocess-invariant'
+export const name = 'subagent-insubprocess-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
diff --git a/packages/subagent/subagent-spawn/package.json b/packages/subagent/subagent-spawn/package.json
index 6ab0d4f45f..647e0b005c 100644
--- a/packages/subagent/subagent-spawn/package.json
+++ b/packages/subagent/subagent-spawn/package.json
@@ -41,7 +41,7 @@
"@deepseek-ai/dsh-agent-loop": "workspace:^",
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:^",
"@deepseek-ai/dsh-bash-local": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-llm-deepseek": "workspace:^",
diff --git a/packages/subagent/subagent-spawn/tests/harness.ts b/packages/subagent/subagent-spawn/tests/harness.ts
index 3bebb612c4..afa1d2a1d2 100644
--- a/packages/subagent/subagent-spawn/tests/harness.ts
+++ b/packages/subagent/subagent-spawn/tests/harness.ts
@@ -3,7 +3,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import AgentLoop from '@deepseek-ai/dsh-agent-loop'
import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import SubagentService from '@deepseek-ai/dsh-subagent'
@@ -28,7 +28,7 @@ export async function spawnHarness(workdir: string): Promise {
})
await ctx.plugin(AgentLoop, { agents: [] })
await ctx.plugin(LlmDeepSeek)
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor, { cwd: workdir, timeoutMs: 30_000 })
await ctx.plugin(ToolBash)
await ctx.plugin(SubagentService)
diff --git a/packages/subagent/subagent-subprocess/src/invariant.ts b/packages/subagent/subagent-subprocess/src/invariant.ts
index c273ce5209..5e401cd738 100644
--- a/packages/subagent/subagent-subprocess/src/invariant.ts
+++ b/packages/subagent/subagent-subprocess/src/invariant.ts
@@ -10,7 +10,7 @@ import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-subagent-subprocess'
/** Cordis companion plugin name. */
-export const name = 'subagent-subprocess-invariant'
+export const name = 'subagent-subsubprocess-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
diff --git a/packages/subprocess/README.md b/packages/subprocess/README.md
new file mode 100644
index 0000000000..53b295d54e
--- /dev/null
+++ b/packages/subprocess/README.md
@@ -0,0 +1,10 @@
+# subprocess/ — subprocess capability family
+
+The shared home for spawning managed child-process groups: fully-specified spawn specs, bounded tail-keep output with spill files, credential-scrubbed environments, offset-based incremental reads, and SIGTERM→grace→SIGKILL group kills. Command defaulting, shell semantics, deadlines, and presentation stay with consumers — the [bash executor family](../bash/README.md) is the first and owning consumer. See the [subprocess seam Agent Note](../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md).
+
+| Package | ctx key | Role |
+|---|---|---|
+| [`subprocess`](subprocess/README.md) (`@deepseek-ai/dsh-subprocess`) | `ctx.subprocess` | The seam: abstract `SubprocessService.spawn(spec)`, the fully-explicit `SubprocessSpawnSpec`, `SubprocessHandle` with offset-based readers, and the shared `DSH_*` managed-environment and `CollectedOutput` vocabulary |
+| [`subprocess-local`](subprocess-local/README.md) (`@deepseek-ai/dsh-subprocess-local`) | — | The local implementation: detached process groups, tail-keep truncation with bounded private spill files, the credential scrub and `DSH_*` merge order, kill escalation, and kill-and-join disposal |
+
+The service owns process lifetime across consumer reloads; consumers own what a process means (a bash command, a future non-shell runner) and every default that shapes one.
diff --git a/packages/process/process-local/README.md b/packages/subprocess/subprocess-local/README.md
similarity index 78%
rename from packages/process/process-local/README.md
rename to packages/subprocess/subprocess-local/README.md
index 595e42af2f..4bc2a71691 100644
--- a/packages/process/process-local/README.md
+++ b/packages/subprocess/subprocess-local/README.md
@@ -1,14 +1,14 @@
-# @deepseek-ai/dsh-process-local
+# @deepseek-ai/dsh-subprocess-local
-Local-subprocess implementation of the [`@deepseek-ai/dsh-process`](../process/README.md) manager seam: `LocalProcessManager` spawns each spec's argv as a detached process group, collects bounded output with size-limited full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. It has no config: every limit and directory arrives on the spawn spec, so the deployment-varying knobs stay with the calling seam's config ([`dsh-bash-local`](../../bash/bash-local/README.md) today).
+Local implementation of the [`@deepseek-ai/dsh-subprocess`](../subprocess/README.md) seam: `LocalSubprocessService` spawns each spec's argv as a detached process group, collects bounded output with size-limited full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. It has no config: every limit and directory arrives on the spawn spec, so the deployment-varying knobs stay with the calling seam's config ([`dsh-bash-local`](../../bash/bash-local/README.md) today).
## Behavior (and where it came from)
- **Detached process groups with escalation** — children are spawned `detached` (own process group); kills send SIGTERM to the group, then SIGKILL after the spec's grace (OpenCode's escalation; pipelines and subshells die with the parent). After the leader exits, inherited stdout/stderr pipes receive the same bounded drain grace so a surviving descendant cannot hold the spawn open indefinitely. ESRCH is tolerated; daemons that re-parent away from the group can still survive — the same caveat as the surveyed tools.
- **Tail-keep truncation + bounded spill files** — output beyond a stream's cap keeps the in-memory TAIL (errors and results cluster at the end — pi/OpenCode rationale) while the FULL stream is appended to a private temp file whose path is reported when available. A stream larger than the spill cap discards its now-incomplete spill and returns only the marked truncated tail; a failed final close withholds the path rather than advertising an incomplete file. Spill files are `0600` with random names under a lazily-created `0700` per-process directory.
- **Credential scrub + managed `DSH_*` merge** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`) and all ambient `DSH_*` names; a spec's ordinary `env` merges after the scrub but rejects `DSH_*`; managed `dshEnv` rejects ordinary names and merges last, preventing stale nested-harness identity. Supplied stdin is written and closed; otherwise fd 0 is `/dev/null`. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
-- **Offset-based reads** — `ProcessHandle` readers return deltas in whole-stream byte coordinates; the manager never holds a cursor, so consumer-owned cursors (the bash background read path) and full-stream re-reads coexist.
-- **Kill-and-join disposal** — the manager retains live handles only so its own disposal can kill every running group and await its exit; settled and spawn-failed handles leave the live set on settlement.
+- **Offset-based reads** — `SubprocessHandle` readers return deltas in whole-stream byte coordinates; the service never holds a cursor, so consumer-owned cursors (the bash background read path) and full-stream re-reads coexist.
+- **Kill-and-join disposal** — the service retains live handles only so its own disposal can kill every running group and await its exit; settled and spawn-failed handles leave the live set on settlement.
## Model Experience
diff --git a/packages/process/process-local/package.json b/packages/subprocess/subprocess-local/package.json
similarity index 83%
rename from packages/process/process-local/package.json
rename to packages/subprocess/subprocess-local/package.json
index 22d51aadff..72ff50c422 100644
--- a/packages/process/process-local/package.json
+++ b/packages/subprocess/subprocess-local/package.json
@@ -1,6 +1,6 @@
{
- "name": "@deepseek-ai/dsh-process-local",
- "description": "Local-subprocess implementation of the DeepSeek Harness process-manager seam",
+ "name": "@deepseek-ai/dsh-subprocess-local",
+ "description": "Local-subprocess implementation of the DeepSeek Harness subprocess seam",
"version": "0.0.1",
"private": true,
"type": "module",
@@ -28,12 +28,12 @@
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
- "@deepseek-ai/dsh-process": "^0.0.1",
+ "@deepseek-ai/dsh-subprocess": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
- "@deepseek-ai/dsh-process": "workspace:^",
+ "@deepseek-ai/dsh-subprocess": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}
diff --git a/packages/process/process-local/src/index.ts b/packages/subprocess/subprocess-local/src/index.ts
similarity index 69%
rename from packages/process/process-local/src/index.ts
rename to packages/subprocess/subprocess-local/src/index.ts
index 6971a58a71..a5256f325b 100644
--- a/packages/process/process-local/src/index.ts
+++ b/packages/subprocess/subprocess-local/src/index.ts
@@ -1,26 +1,26 @@
/**
- * Local-subprocess implementation of the process-manager seam. Each spawn is
+ * Local-subprocess implementation of the subprocess seam. Each spawn is
* a detached process group with bounded, spill-backed output; disposal kills
* and joins live groups. It has no config: every limit arrives on the spec,
* so the deployment-varying choices stay with the calling seam's config (the
* bash executor's, today).
- * @module @deepseek-ai/dsh-process-local
+ * @module @deepseek-ai/dsh-subprocess-local
*/
import { Context } from 'cordis'
-import { ProcessManager } from '@deepseek-ai/dsh-process'
-import type { ProcessHandle, ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
+import { SubprocessService } from '@deepseek-ai/dsh-subprocess'
+import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
import { spawnProcess } from './spawn.ts'
import type { SpawnInternals } from './spawn.ts'
/**
- * Local process manager: detached process groups, tail-keep truncation with
+ * Local subprocess service: detached process groups, tail-keep truncation with
* bounded spill files, credential-scrubbed environment, and group
* SIGTERM→grace→SIGKILL escalation.
*/
-export class LocalProcessManager extends ProcessManager {
+export class LocalSubprocessService extends SubprocessService {
/** Live handles retained only so disposal can kill and join them. */
- private live = new Set()
+ private live = new Set()
/** Test seam: spill knobs forwarded to spawnProcess. */
internals: SpawnInternals = {}
@@ -36,10 +36,10 @@ export class LocalProcessManager extends ProcessManager {
}
this.live.clear()
await Promise.all(pending)
- }, 'local process-manager teardown')
+ }, 'local subprocess teardown')
}
- spawn(spec: ProcessSpawnSpec): ProcessHandle {
+ spawn(spec: SubprocessSpawnSpec): SubprocessHandle {
const handle = spawnProcess(spec, this.internals)
this.live.add(handle)
handle.done.then(
@@ -50,4 +50,4 @@ export class LocalProcessManager extends ProcessManager {
}
}
-export default LocalProcessManager
+export default LocalSubprocessService
diff --git a/packages/process/process-local/src/invariant.ts b/packages/subprocess/subprocess-local/src/invariant.ts
similarity index 77%
rename from packages/process/process-local/src/invariant.ts
rename to packages/subprocess/subprocess-local/src/invariant.ts
index fa6c0c810e..b15b2dd511 100644
--- a/packages/process/process-local/src/invariant.ts
+++ b/packages/subprocess/subprocess-local/src/invariant.ts
@@ -1,16 +1,16 @@
/**
- * Package-owned invariant companion for `@deepseek-ai/dsh-process-local`.
- * @module @deepseek-ai/dsh-process-local/invariant
+ * Package-owned invariant companion for `@deepseek-ai/dsh-subprocess-local`.
+ * @module @deepseek-ai/dsh-subprocess-local/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
-const PACKAGE_NAME = '@deepseek-ai/dsh-process-local'
+const PACKAGE_NAME = '@deepseek-ai/dsh-subprocess-local'
/** Cordis companion plugin name. */
-export const name = 'process-local-invariant'
+export const name = 'subprocess-local-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
diff --git a/packages/process/process-local/src/spawn.ts b/packages/subprocess/subprocess-local/src/spawn.ts
similarity index 92%
rename from packages/process/process-local/src/spawn.ts
rename to packages/subprocess/subprocess-local/src/spawn.ts
index 747b85cf13..95b391ea4b 100644
--- a/packages/process/process-local/src/spawn.ts
+++ b/packages/subprocess/subprocess-local/src/spawn.ts
@@ -1,9 +1,9 @@
/**
- * Process plumbing for the local process manager: detached process-group
+ * Process plumbing for the local subprocess service: detached process-group
* spawn, tail-keep output with spill files, and SIGTERM→SIGKILL escalation.
* This layer reacts to an abort signal; callers own deadlines and classify
* causes.
- * @module dsh-process-local/spawn
+ * @module dsh-subprocess-local/spawn
*/
import { type ChildProcessByStdio, spawn } from 'node:child_process'
@@ -12,8 +12,8 @@ import { randomBytes } from 'node:crypto'
import { closeSync, mkdtempSync, openSync, unlinkSync, writeSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
-import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-process'
-import type { CollectedOutput, DshEnvironment, ProcessHandle, ProcessOutcome, ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
+import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-subprocess'
+import type { CollectedOutput, DshEnvironment, SubprocessHandle, SubprocessOutcome, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
/**
* Credential-shaped env vars are NOT forwarded to children (the harness's
@@ -68,7 +68,7 @@ let defaultSpillDir: string | undefined
* other local users read command output or pre-create symlinks.
*/
function privateSpillDir(): string {
- defaultSpillDir ??= mkdtempSync(join(tmpdir(), 'dsh-proc-'))
+ defaultSpillDir ??= mkdtempSync(join(tmpdir(), 'dsh-subprocess-'))
return defaultSpillDir
}
@@ -140,7 +140,7 @@ export class OutputCollector {
// prediction and symlink planting in shared tmp dirs.
this.spillFile = join(
this.spillDir,
- `dsh-proc-${process.pid}-${++spillCounter}-${randomBytes(6).toString('hex')}-${this.label}.log`,
+ `dsh-subprocess-${process.pid}-${++spillCounter}-${randomBytes(6).toString('hex')}-${this.label}.log`,
)
this.spillFd = openSync(this.spillFile, 'wx', 0o600)
for (const prior of this.chunks) writeSync(this.spillFd, prior)
@@ -236,12 +236,12 @@ export function killGroup(pid: number, sig: NodeJS.Signals): void {
/**
* Spawn one isolated detached process group and collect its output.
- * Runtime exits resolve as {@link ProcessOutcome}; only spawn failures reject.
+ * Runtime exits resolve as {@link SubprocessOutcome}; only spawn failures reject.
* @param spec - fully resolved argv, cwd, limits, and cancellation.
* @param internals - test-only spill-directory override.
* @returns live process handle and outcome promise.
*/
-export function spawnProcess(spec: ProcessSpawnSpec, internals: SpawnInternals = {}): ProcessHandle {
+export function spawnProcess(spec: SubprocessSpawnSpec, internals: SpawnInternals = {}): SubprocessHandle {
const spillDir = internals.spillDir ?? privateSpillDir()
if (spec.signal?.aborted) {
@@ -264,12 +264,17 @@ export function spawnProcess(spec: ProcessSpawnSpec, internals: SpawnInternals =
child.stderr.on('data', (chunk: Buffer) => { stderr.push(chunk) })
let graceTimer: NodeJS.Timeout | undefined
+ let settled = false
// Failed spawns use pid -1 so kill remains a no-op.
const pid = child.pid ?? -1
const kill = (): void => {
if (graceTimer !== undefined) return // escalation already in flight
+ // After settlement the group is gone and the pid may be reused; callers
+ // commonly kill() in a finally, so this must not re-signal or start a
+ // timer that outlives the handle.
+ if (settled) return
killGroup(pid, 'SIGTERM')
graceTimer = setTimeout(() => { killGroup(pid, 'SIGKILL') }, spec.graceMs)
}
@@ -284,8 +289,7 @@ export function spawnProcess(spec: ProcessSpawnSpec, internals: SpawnInternals =
child.stdin.end(spec.stdin)
}
- const done = new Promise((resolve, reject) => {
- let settled = false
+ const done = new Promise((resolve, reject) => {
let pipeDrainTimer: NodeJS.Timeout | undefined
const settle = (exitCode: number | null, signal: NodeJS.Signals | null): void => {
if (settled) return
diff --git a/packages/process/process-local/tests/manager.spec.ts b/packages/subprocess/subprocess-local/tests/local.spec.ts
similarity index 57%
rename from packages/process/process-local/tests/manager.spec.ts
rename to packages/subprocess/subprocess-local/tests/local.spec.ts
index 719dcc1d44..d6f81042b5 100644
--- a/packages/process/process-local/tests/manager.spec.ts
+++ b/packages/subprocess/subprocess-local/tests/local.spec.ts
@@ -1,9 +1,9 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
-import type { ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
+import type { SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
-function spec(command: string, overrides: Partial = {}): ProcessSpawnSpec {
+function spec(command: string, overrides: Partial = {}): SubprocessSpawnSpec {
return {
argv: ['bash', '-c', command],
cwd: process.cwd(),
@@ -15,11 +15,11 @@ function spec(command: string, overrides: Partial = {}): Proce
}
}
-describe('LocalProcessManager', () => {
- it('registers as ctx.processes and spawns managed handles', async () => {
+describe('LocalSubprocessService', () => {
+ it('registers as ctx.subprocess and spawns managed handles', async () => {
const ctx = new Context()
- const fiber = await ctx.plugin(LocalProcessManager)
- const result = await ctx.processes.spawn(spec('echo managed')).done
+ const fiber = await ctx.plugin(LocalSubprocessService)
+ const result = await ctx.subprocess.spawn(spec('echo managed')).done
expect(result.exitCode).toBe(0)
expect(result.stdout.text).toBe('managed\n')
await fiber.dispose()
@@ -27,8 +27,8 @@ describe('LocalProcessManager', () => {
it('disposal kills still-running processes and awaits their exit', async () => {
const ctx = new Context()
- const fiber = await ctx.plugin(LocalProcessManager)
- const handle = ctx.processes.spawn(spec('sleep 60'))
+ const fiber = await ctx.plugin(LocalSubprocessService)
+ const handle = ctx.subprocess.spawn(spec('sleep 60'))
await fiber.dispose()
const outcome = await handle.done
expect(outcome.signal).toBe('SIGTERM')
@@ -36,8 +36,8 @@ describe('LocalProcessManager', () => {
it('a settled process leaves the live set (disposal does not re-kill it)', async () => {
const ctx = new Context()
- const fiber = await ctx.plugin(LocalProcessManager)
- const handle = ctx.processes.spawn(spec('true'))
+ const fiber = await ctx.plugin(LocalSubprocessService)
+ const handle = ctx.subprocess.spawn(spec('true'))
const outcome = await handle.done
expect(outcome.exitCode).toBe(0)
await fiber.dispose()
@@ -45,26 +45,26 @@ describe('LocalProcessManager', () => {
it('disposal tolerates a handle whose spawn already failed', async () => {
const ctx = new Context()
- const fiber = await ctx.plugin(LocalProcessManager)
- const handle = ctx.processes.spawn(spec('true', { cwd: '/nonexistent-dir-dsh-manager-test' }))
+ const fiber = await ctx.plugin(LocalSubprocessService)
+ const handle = ctx.subprocess.spawn(spec('true', { cwd: '/nonexistent-dir-dsh-subprocess-test' }))
await expect(handle.done).rejects.toThrow()
await fiber.dispose()
})
it('disposal contains a spawn-failure rejection that races teardown', async () => {
const ctx = new Context()
- const fiber = await ctx.plugin(LocalProcessManager)
+ const fiber = await ctx.plugin(LocalSubprocessService)
// Dispose before the rejection continuation removes the handle from the
// live set, so teardown itself must swallow the rejected done.
- const handle = ctx.processes.spawn(spec('true', { cwd: '/nonexistent-dir-dsh-manager-test' }))
+ const handle = ctx.subprocess.spawn(spec('true', { cwd: '/nonexistent-dir-dsh-subprocess-test' }))
await fiber.dispose()
await expect(handle.done).rejects.toThrow()
})
it('loading a second implementation throws (one processes service per context — cordis standard)', async () => {
const ctx = new Context()
- await ctx.plugin(LocalProcessManager)
- class SecondManager extends LocalProcessManager {}
- await expect(ctx.plugin(SecondManager)).rejects.toThrow(/service "processes" has been registered/)
+ await ctx.plugin(LocalSubprocessService)
+ class SecondManager extends LocalSubprocessService {}
+ await expect(ctx.plugin(SecondManager)).rejects.toThrow(/service "subprocess" has been registered/)
})
})
diff --git a/packages/process/process-local/tests/spawn.spec.ts b/packages/subprocess/subprocess-local/tests/spawn.spec.ts
similarity index 95%
rename from packages/process/process-local/tests/spawn.spec.ts
rename to packages/subprocess/subprocess-local/tests/spawn.spec.ts
index 954196e104..1bc2b27498 100644
--- a/packages/process/process-local/tests/spawn.spec.ts
+++ b/packages/subprocess/subprocess-local/tests/spawn.spec.ts
@@ -2,9 +2,9 @@ import { mkdtempSync, readFileSync, statSync, unlinkSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
-import type { DshEnvironment } from '@deepseek-ai/dsh-process'
+import type { DshEnvironment } from '@deepseek-ai/dsh-subprocess'
import { killGroup, OutputCollector, spawnProcess } from '../src/spawn.ts'
-import type { ProcessHandle } from '@deepseek-ai/dsh-process'
+import type { SubprocessHandle } from '@deepseek-ai/dsh-subprocess'
const { failNextClose, failNextUnlink } = vi.hoisted(() => ({
failNextClose: { value: false },
@@ -31,7 +31,7 @@ vi.mock('node:fs', async (importOriginal) => {
}
})
-const spillDir = mkdtempSync(join(tmpdir(), 'dsh-proc-spec-'))
+const spillDir = mkdtempSync(join(tmpdir(), 'dsh-subprocess-spec-'))
function spec(command: string, overrides: Partial[0]> = {}) {
return {
@@ -59,7 +59,7 @@ async function waitGone(pid: number, timeoutMs = 5_000): Promise {
throw new Error(`pid ${pid} still alive after ${timeoutMs}ms`)
}
-async function waitForStdout(running: ProcessHandle, expected: string, timeoutMs = 5_000): Promise {
+async function waitForStdout(running: SubprocessHandle, expected: string, timeoutMs = 5_000): Promise {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
if (running.stdout.readFrom(0).text.includes(expected)) return
@@ -407,6 +407,21 @@ describe('killGroup', () => {
await running.done
expect(() => { killGroup(running.pid, 'SIGTERM') }).not.toThrow()
})
+
+ it('handle.kill() after settlement signals nothing and starts no grace timer', async () => {
+ // Cleanup code commonly kills handles in a finally; after settlement the
+ // group is gone and the pid may be reused, so a late kill must be inert
+ // (no signal to a possibly-recycled pgid, no referenced timer delaying exit).
+ const running = spawnProcess(spec('true'))
+ await running.done
+ const spy = vi.spyOn(process, 'kill')
+ try {
+ running.kill()
+ expect(spy).not.toHaveBeenCalled()
+ } finally {
+ spy.mockRestore()
+ }
+ })
})
describe('argv validation', () => {
@@ -490,7 +505,7 @@ describe('environment and spill-file hardening', () => {
{ spillDir },
).done
const path = result.stdout.spillPath!
- expect(path).toMatch(/dsh-proc-\d+-\d+-[0-9a-f]{12}-stdout\.log$/)
+ expect(path).toMatch(/dsh-subprocess-\d+-\d+-[0-9a-f]{12}-stdout\.log$/)
const mode = statSync(path).mode & 0o777
expect(mode).toBe(0o600)
})
@@ -500,7 +515,7 @@ describe('environment and spill-file hardening', () => {
spec('for i in $(seq 1 200); do printf "line-%04d\\n" $i; done', { stdoutMaxBytes: 500, stderrMaxBytes: 500 }),
).done
const dir = dirname(result.stdout.spillPath!)
- expect(dir).toMatch(/dsh-proc-/)
+ expect(dir).toMatch(/dsh-subprocess-/)
const mode = statSync(dir).mode & 0o777
expect(mode).toBe(0o700)
})
diff --git a/packages/process/process-local/tsconfig.json b/packages/subprocess/subprocess-local/tsconfig.json
similarity index 92%
rename from packages/process/process-local/tsconfig.json
rename to packages/subprocess/subprocess-local/tsconfig.json
index 5f84f780af..5a8dea211b 100644
--- a/packages/process/process-local/tsconfig.json
+++ b/packages/subprocess/subprocess-local/tsconfig.json
@@ -15,7 +15,7 @@
"path": "../../../vendor/cordis"
},
{
- "path": "../process"
+ "path": "../subprocess"
},
{
"path": "../../support/invariants"
diff --git a/packages/process/process/README.md b/packages/subprocess/subprocess/README.md
similarity index 61%
rename from packages/process/process/README.md
rename to packages/subprocess/subprocess/README.md
index b53628cab3..15165a730d 100644
--- a/packages/process/process/README.md
+++ b/packages/subprocess/subprocess/README.md
@@ -1,16 +1,16 @@
-# @deepseek-ai/dsh-process
+# @deepseek-ai/dsh-subprocess
-The child-process manager seam (`ctx.processes`). The abstract `ProcessManager` exposes one method — `spawn(spec): ProcessHandle` — plus the vocabulary shared by every consumer: the fully-explicit `ProcessSpawnSpec`, `ProcessHandle` with its non-consuming offset-based output readers, `ProcessOutcome`, `CollectedOutput`, and the managed `DSH_*` environment namespace (`DSH_ENV_PREFIX`, `DshEnvironment`). The local implementation lives in [`dsh-process-local`](../process-local/README.md).
+The subprocess seam (`ctx.subprocess`). The abstract `SubprocessService` exposes one method — `spawn(spec): SubprocessHandle` — plus the vocabulary shared by every consumer: the fully-explicit `SubprocessSpawnSpec`, `SubprocessHandle` with its non-consuming offset-based output readers, `SubprocessOutcome`, `CollectedOutput`, and the managed `DSH_*` environment namespace (`DSH_ENV_PREFIX`, `DshEnvironment`). The local implementation lives in [`dsh-subprocess-local`](../subprocess-local/README.md).
## Contract
- `spawn(spec)` returns immediately with a live handle; `done` resolves at process close and rejects only for spawn-level failures.
-- The spec is fully explicit — argv, cwd, per-stream byte caps, spill cap, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden process-manager default (the `dsh-bash` request/spec split is the owning template). `argv` is never shell-interpreted here; a consumer that wants a shell passes `['bash', '-c', command]` itself.
+- The spec is fully explicit — argv, cwd, per-stream byte caps, spill cap, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden subprocess-service default (the `dsh-bash` request/spec split is the owning template). `argv` is never shell-interpreted here; a consumer that wants a shell passes `['bash', '-c', command]` itself.
- Output readers take whole-stream byte offsets and never consume: independent readers cannot steal one another's deltas. A read whose offset slid out of the in-memory tail is `lossy` and points at the full-stream spill file when one exists.
-- `kill()` and the spec's abort signal escalate SIGTERM→grace→SIGKILL across the whole detached group; the manager reacts to the abort but never classifies why (callers own deadlines and cause classification).
+- `kill()` and the spec's abort signal escalate SIGTERM→grace→SIGKILL across the whole detached group; the service reacts to the abort but never classifies why (callers own deadlines and cause classification).
- Disposal kills all still-running managed processes and awaits their exit.
-See the [process data-structure catalog](../../../docs/core-data-structures/process.md) and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-process-manager-seam.md).
+See the [process data-structure catalog](../../../docs/core-data-structures/subprocess.md) and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-subprocess-seam.md).
## Model Experience
diff --git a/packages/process/process/package.json b/packages/subprocess/subprocess/package.json
similarity index 77%
rename from packages/process/process/package.json
rename to packages/subprocess/subprocess/package.json
index 6210948d56..6771c19651 100644
--- a/packages/process/process/package.json
+++ b/packages/subprocess/subprocess/package.json
@@ -1,6 +1,6 @@
{
- "name": "@deepseek-ai/dsh-process",
- "description": "Child-process manager seam (ctx.processes) for the DeepSeek Harness — managed process groups, bounded spill-backed output, and escalated kills behind one abstract service",
+ "name": "@deepseek-ai/dsh-subprocess",
+ "description": "Subprocess seam (ctx.subprocess) for the DeepSeek Harness — managed process groups, bounded spill-backed output, and escalated kills behind one abstract service",
"version": "0.0.1",
"private": true,
"type": "module",
diff --git a/packages/process/process/src/index.ts b/packages/subprocess/subprocess/src/index.ts
similarity index 65%
rename from packages/process/process/src/index.ts
rename to packages/subprocess/subprocess/src/index.ts
index 4fe3386503..890cf74e61 100644
--- a/packages/process/process/src/index.ts
+++ b/packages/subprocess/subprocess/src/index.ts
@@ -1,37 +1,37 @@
/**
- * The child-process manager seam (`ctx.processes`): spawn fully-specified
+ * The subprocess seam (`ctx.subprocess`): spawn fully-specified
* commands into managed process groups with bounded, spill-backed output and
* escalated kills. Command defaulting, shell semantics, deadlines, and
* presentation belong to consumers — the bash executor seam is the owning
* template. The local implementation lives in
- * `@deepseek-ai/dsh-process-local`.
- * @module @deepseek-ai/dsh-process
+ * `@deepseek-ai/dsh-subprocess-local`.
+ * @module @deepseek-ai/dsh-subprocess
*/
import { Context, Service } from 'cordis'
-import type { ProcessHandle, ProcessSpawnSpec } from './types.ts'
+import type { SubprocessHandle, SubprocessSpawnSpec } from './types.ts'
export { DSH_ENV_PREFIX } from './types.ts'
export type {
CollectedOutput,
DshEnvironment,
DshEnvironmentKey,
- ProcessHandle,
- ProcessOutcome,
- ProcessOutputRead,
- ProcessOutputReader,
- ProcessSpawnSpec,
+ SubprocessHandle,
+ SubprocessOutcome,
+ SubprocessOutputRead,
+ SubprocessOutputReader,
+ SubprocessSpawnSpec,
} from './types.ts'
declare module 'cordis' {
interface Context {
- processes: ProcessManager
+ subprocess: SubprocessService
}
}
/**
- * Abstract process manager. Subclass, implement {@link spawn}, and load the
- * subclass as a plugin — it registers as `ctx.processes` (one implementation
+ * Abstract subprocess service. Subclass, implement {@link spawn}, and load the
+ * subclass as a plugin — it registers as `ctx.subprocess` (one implementation
* per context; loading a second throws, which is cordis' standard
* duplicate-service behavior).
*
@@ -41,13 +41,13 @@ declare module 'cordis' {
* - Output readers are offset-based and non-consuming, so independent readers
* never consume one another's output; lossy reads report truncation and the
* spill file holding the complete stream when one exists.
- * - {@link ProcessHandle.kill} and the spec's abort signal escalate
+ * - {@link SubprocessHandle.kill} and the spec's abort signal escalate
* SIGTERM→grace→SIGKILL across the whole process group.
* - Disposal kills all still-running managed processes and awaits their exit.
*/
-export abstract class ProcessManager extends Service {
+export abstract class SubprocessService extends Service {
constructor(ctx: Context) {
- super(ctx, 'processes')
+ super(ctx, 'subprocess')
}
/**
@@ -56,7 +56,7 @@ export abstract class ProcessManager extends Service {
* @param spec - argv, directory, limits, grace, cancellation, and environment.
* @returns the live process handle (readers, kill, outcome promise).
*/
- abstract spawn(spec: ProcessSpawnSpec): ProcessHandle
+ abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle
}
-export default ProcessManager
+export default SubprocessService
diff --git a/packages/process/process/src/invariant.ts b/packages/subprocess/subprocess/src/invariant.ts
similarity index 73%
rename from packages/process/process/src/invariant.ts
rename to packages/subprocess/subprocess/src/invariant.ts
index f54476747e..62a720babd 100644
--- a/packages/process/process/src/invariant.ts
+++ b/packages/subprocess/subprocess/src/invariant.ts
@@ -1,12 +1,12 @@
-/** Package-owned invariant companion for the process-manager seam. @module @deepseek-ai/dsh-process/invariant */
+/** Package-owned invariant companion for the subprocess seam. @module @deepseek-ai/dsh-subprocess/invariant */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
-const PACKAGE_NAME = '@deepseek-ai/dsh-process'
+const PACKAGE_NAME = '@deepseek-ai/dsh-subprocess'
/** Cordis companion plugin name. */
-export const name = 'process-invariant'
+export const name = 'subprocess-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
@@ -14,7 +14,7 @@ export const inject = ['invariants']
const install: InvariantInstaller = () => {}
/**
- * Register the process-manager invariant companion.
+ * Register the subprocess invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
diff --git a/packages/process/process/src/types.ts b/packages/subprocess/subprocess/src/types.ts
similarity index 87%
rename from packages/process/process/src/types.ts
rename to packages/subprocess/subprocess/src/types.ts
index 1b81ab8c07..a5ec7f4e55 100644
--- a/packages/process/process/src/types.ts
+++ b/packages/subprocess/subprocess/src/types.ts
@@ -1,9 +1,9 @@
/**
- * Vocabulary for the process-manager seam: fully-specified spawn requests,
+ * Vocabulary for the subprocess seam: fully-specified spawn requests,
* bounded output with spill recovery, and live process handles. Command
* defaulting, shell semantics, and presentation belong to consumers such as
* the bash executor seam.
- * @module dsh-process/types
+ * @module dsh-subprocess/types
*/
/** Namespace prefix reserved for DeepSeek Harness-managed child environment facts. */
@@ -28,10 +28,10 @@ export interface CollectedOutput {
/**
* A fully-specified spawn request. This seam applies no defaults: every limit
* and directory is explicit, so the caller's own config — not a hidden
- * process-manager default — decides them (the `dsh-bash` request/spec split
+ * subprocess-service default — decides them (the `dsh-bash` request/spec split
* is the owning template).
*/
-export interface ProcessSpawnSpec {
+export interface SubprocessSpawnSpec {
/** Executable and arguments; `argv[0]` is the program. Never shell-interpreted here. */
argv: readonly string[]
/** Working directory for the child. */
@@ -70,10 +70,10 @@ export interface ProcessSpawnSpec {
/**
* Raw outcome of one closed process. Deliberately carries NO timeout or
- * cancellation classification: the manager kills on abort but does not decide
+ * cancellation classification: the service kills on abort but does not decide
* why — the caller reads the signal it owns to classify causes.
*/
-export interface ProcessOutcome {
+export interface SubprocessOutcome {
/** Exit code; null when the process died from a signal. */
exitCode: number | null
/** Terminating signal (e.g. 'SIGTERM'); null on normal exit. */
@@ -82,8 +82,8 @@ export interface ProcessOutcome {
stderr: CollectedOutput
}
-/** One incremental {@link ProcessOutputReader.readFrom} read. */
-export interface ProcessOutputRead {
+/** One incremental {@link SubprocessOutputReader.readFrom} read. */
+export interface SubprocessOutputRead {
/** Stream text from the requested offset (the whole retained tail when lossy). */
text: string
/** Whole-stream offset to resume from on the next read. */
@@ -99,7 +99,7 @@ export interface ProcessOutputRead {
* whole-stream byte coordinates owned by the caller, so independent readers
* cannot consume one another's output.
*/
-export interface ProcessOutputReader {
+export interface SubprocessOutputReader {
/**
* Read everything captured since `fromByte`. When that offset has slid out
* of the in-memory tail window the read is `lossy` — it returns the whole
@@ -107,22 +107,22 @@ export interface ProcessOutputReader {
* @param fromByte - whole-stream offset to resume from (a prior read's `nextOffset`; 0 for the first read).
* @returns the delta text, the next offset, the `lossy` flag, and the spill path when one exists.
*/
- readFrom(fromByte: number): ProcessOutputRead
+ readFrom(fromByte: number): SubprocessOutputRead
}
/**
* A live child process. `kill()` starts the group SIGTERM→grace→SIGKILL
* escalation; buffered output remains readable after exit.
*/
-export interface ProcessHandle {
+export interface SubprocessHandle {
/** Process id (group leader); -1 when the spawn itself failed. */
readonly pid: number
/** Live stdout reader (also readable after exit). */
- readonly stdout: ProcessOutputReader
+ readonly stdout: SubprocessOutputReader
/** Live stderr reader (also readable after exit). */
- readonly stderr: ProcessOutputReader
+ readonly stderr: SubprocessOutputReader
/** Resolves when the process closes; rejects only for spawn-level failures. */
- readonly done: Promise
+ readonly done: Promise
/** Begin SIGTERM→grace→SIGKILL on the process group. Idempotent. */
kill(): void
}
diff --git a/packages/process/process/tests/service.spec.ts b/packages/subprocess/subprocess/tests/service.spec.ts
similarity index 60%
rename from packages/process/process/tests/service.spec.ts
rename to packages/subprocess/subprocess/tests/service.spec.ts
index 04ffa8c9f4..630aa35f1a 100644
--- a/packages/process/process/tests/service.spec.ts
+++ b/packages/subprocess/subprocess/tests/service.spec.ts
@@ -1,16 +1,16 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
-import { ProcessManager } from '@deepseek-ai/dsh-process'
-import type { ProcessHandle, ProcessOutputRead, ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
+import { SubprocessService } from '@deepseek-ai/dsh-subprocess'
+import type { SubprocessHandle, SubprocessOutputRead, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
/**
- * Minimal concrete manager: a hand-built handle. The seam is spawn-only —
+ * Minimal concrete service: a hand-built handle. The seam is spawn-only —
* defaulting, shell semantics, and deadlines belong to callers — so this stub
* is all an implementation owes the abstract class.
*/
-class StubProcessManager extends ProcessManager {
- spawn(spec: ProcessSpawnSpec): ProcessHandle {
- const read: ProcessOutputRead = { text: '', nextOffset: 0, lossy: false }
+class StubSubprocessService extends SubprocessService {
+ spawn(spec: SubprocessSpawnSpec): SubprocessHandle {
+ const read: SubprocessOutputRead = { text: '', nextOffset: 0, lossy: false }
let killed = false
return {
pid: spec.argv.length,
@@ -27,11 +27,11 @@ class StubProcessManager extends ProcessManager {
}
}
-describe('ProcessManager seam', () => {
- it('a concrete subclass registers as ctx.processes and serves the abstract API', async () => {
+describe('SubprocessService seam', () => {
+ it('a concrete subclass registers as ctx.subprocess and serves the abstract API', async () => {
const ctx = new Context()
- await ctx.plugin(StubProcessManager)
- const handle = ctx.processes.spawn({
+ await ctx.plugin(StubSubprocessService)
+ const handle = ctx.subprocess.spawn({
argv: ['true'],
cwd: '/stub',
stdoutMaxBytes: 1,
@@ -48,8 +48,8 @@ describe('ProcessManager seam', () => {
it('loading a second implementation throws (one processes service per context — cordis standard)', async () => {
const ctx = new Context()
- await ctx.plugin(StubProcessManager)
- class SecondManager extends StubProcessManager {}
- await expect(ctx.plugin(SecondManager)).rejects.toThrow(/service "processes" has been registered/)
+ await ctx.plugin(StubSubprocessService)
+ class SecondManager extends StubSubprocessService {}
+ await expect(ctx.plugin(SecondManager)).rejects.toThrow(/service "subprocess" has been registered/)
})
})
diff --git a/packages/process/process/tsconfig.json b/packages/subprocess/subprocess/tsconfig.json
similarity index 100%
rename from packages/process/process/tsconfig.json
rename to packages/subprocess/subprocess/tsconfig.json
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 340d47c7fd..82f3691044 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -182,9 +182,6 @@ importers:
'@deepseek-ai/dsh-paths':
specifier: workspace:^
version: link:../../packages/util/paths
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../packages/process/process-local
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../packages/core/session
@@ -227,6 +224,9 @@ importers:
'@deepseek-ai/dsh-subagent-spawn':
specifier: workspace:^
version: link:../../packages/subagent/subagent-spawn
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../packages/subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../packages/core/system-prompt
@@ -427,9 +427,6 @@ importers:
'@deepseek-ai/dsh-plan-mode':
specifier: workspace:*
version: link:../packages/plan/plan-mode
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:*
- version: link:../packages/process/process-local
'@deepseek-ai/dsh-pty':
specifier: workspace:*
version: link:../packages/pty/pty
@@ -478,6 +475,9 @@ importers:
'@deepseek-ai/dsh-subagent-spawn':
specifier: workspace:*
version: link:../packages/subagent/subagent-spawn
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:*
+ version: link:../packages/subprocess/subprocess-local
'@deepseek-ai/dsh-tasks-local':
specifier: workspace:*
version: link:../packages/tasks/tasks-local
@@ -591,12 +591,12 @@ importers:
'@deepseek-ai/dsh-invariants':
specifier: workspace:^
version: link:../../support/invariants
- '@deepseek-ai/dsh-process':
- specifier: workspace:^
- version: link:../../process/process
'@deepseek-ai/dsh-sandbox':
specifier: workspace:^
version: link:../../sandbox/sandbox
+ '@deepseek-ai/dsh-subprocess':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess
cordis:
specifier: ^4.0.0-rc.7
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
@@ -613,12 +613,12 @@ importers:
'@deepseek-ai/dsh-invariants':
specifier: workspace:^
version: link:../../support/invariants
- '@deepseek-ai/dsh-process':
+ '@deepseek-ai/dsh-subprocess':
specifier: workspace:^
- version: link:../../process/process
- '@deepseek-ai/dsh-process-local':
+ version: link:../../subprocess/subprocess
+ '@deepseek-ai/dsh-subprocess-local':
specifier: workspace:^
- version: link:../../process/process-local
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-timeout':
specifier: workspace:^
version: link:../../util/timeout
@@ -637,9 +637,6 @@ importers:
'@deepseek-ai/dsh-invariants':
specifier: workspace:^
version: link:../../support/invariants
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-sandbox':
specifier: workspace:^
version: link:../../sandbox/sandbox
@@ -649,6 +646,9 @@ importers:
'@deepseek-ai/dsh-sandbox-policy':
specifier: workspace:^
version: link:../../sandbox/sandbox-policy
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
cordis:
specifier: ^4.0.0-rc.7
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
@@ -686,9 +686,6 @@ importers:
'@deepseek-ai/dsh-paths':
specifier: workspace:^
version: link:../../util/paths
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-sandbox':
specifier: workspace:^
version: link:../../sandbox/sandbox
@@ -704,6 +701,9 @@ importers:
'@deepseek-ai/dsh-session-persistence-jsonl':
specifier: workspace:^
version: link:../../session-persistence/session-persistence-jsonl
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../core/system-prompt
@@ -1658,9 +1658,6 @@ importers:
'@deepseek-ai/dsh-paths':
specifier: workspace:^
version: link:../../util/paths
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-sandbox-local':
specifier: workspace:^
version: link:../../sandbox/sandbox-local
@@ -1682,6 +1679,9 @@ importers:
'@deepseek-ai/dsh-skill-local':
specifier: workspace:^
version: link:../../skill/skill-local
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../core/system-prompt
@@ -2004,9 +2004,6 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-retention':
specifier: workspace:^
version: link:../../util/retention
@@ -2016,6 +2013,9 @@ importers:
'@deepseek-ai/dsh-spill':
specifier: workspace:^
version: link:../../spill/spill
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../core/system-prompt
@@ -2227,9 +2227,6 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
@@ -2242,6 +2239,9 @@ importers:
'@deepseek-ai/dsh-subagent':
specifier: workspace:^
version: link:../../subagent/subagent
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-tools':
specifier: workspace:^
version: link:../../core/tools
@@ -2279,9 +2279,6 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:^
version: link:../../llm/llm
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
@@ -2291,6 +2288,9 @@ importers:
'@deepseek-ai/dsh-session-persistence-jsonl':
specifier: workspace:^
version: link:../../session-persistence/session-persistence-jsonl
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-tools':
specifier: workspace:^
version: link:../../core/tools
@@ -2646,27 +2646,6 @@ importers:
specifier: ^4.0.0-rc.7
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
- packages/process/process:
- devDependencies:
- '@deepseek-ai/dsh-invariants':
- specifier: workspace:^
- version: link:../../support/invariants
- cordis:
- specifier: ^4.0.0-rc.7
- version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
-
- packages/process/process-local:
- devDependencies:
- '@deepseek-ai/dsh-invariants':
- specifier: workspace:^
- version: link:../../support/invariants
- '@deepseek-ai/dsh-process':
- specifier: workspace:^
- version: link:../process
- cordis:
- specifier: ^4.0.0-rc.7
- version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
-
packages/pty/pty:
devDependencies:
'@deepseek-ai/dsh-agent':
@@ -3623,9 +3602,6 @@ importers:
'@deepseek-ai/dsh-llm-deepseek':
specifier: workspace:^
version: link:../../llm/llm-deepseek
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../process/process-local
'@deepseek-ai/dsh-session':
specifier: workspace:^
version: link:../../core/session
@@ -3635,6 +3611,9 @@ importers:
'@deepseek-ai/dsh-subagent-inprocess':
specifier: workspace:^
version: link:../subagent-inprocess
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../subprocess/subprocess-local
'@deepseek-ai/dsh-tool-bash':
specifier: workspace:^
version: link:../../bash/tool-bash
@@ -3694,6 +3673,27 @@ importers:
specifier: ^4.0.0-rc.7
version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
+ packages/subprocess/subprocess:
+ devDependencies:
+ '@deepseek-ai/dsh-invariants':
+ specifier: workspace:^
+ version: link:../../support/invariants
+ cordis:
+ specifier: ^4.0.0-rc.7
+ version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
+
+ packages/subprocess/subprocess-local:
+ devDependencies:
+ '@deepseek-ai/dsh-invariants':
+ specifier: workspace:^
+ version: link:../../support/invariants
+ '@deepseek-ai/dsh-subprocess':
+ specifier: workspace:^
+ version: link:../subprocess
+ cordis:
+ specifier: ^4.0.0-rc.7
+ version: 4.0.0-rc.7(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.5)
+
packages/support/acp-snapshot:
dependencies:
'@agentclientprotocol/sdk':
@@ -4639,12 +4639,6 @@ importers:
'@deepseek-ai/dsh-plan-mode':
specifier: workspace:^
version: link:../../packages/plan/plan-mode
- '@deepseek-ai/dsh-process':
- specifier: workspace:^
- version: link:../../packages/process/process
- '@deepseek-ai/dsh-process-local':
- specifier: workspace:^
- version: link:../../packages/process/process-local
'@deepseek-ai/dsh-repeat-tool-guard':
specifier: workspace:^
version: link:../../packages/guard/repeat-tool-guard
@@ -4711,6 +4705,12 @@ importers:
'@deepseek-ai/dsh-subagent-subprocess':
specifier: workspace:^
version: link:../../packages/subagent/subagent-subprocess
+ '@deepseek-ai/dsh-subprocess':
+ specifier: workspace:^
+ version: link:../../packages/subprocess/subprocess
+ '@deepseek-ai/dsh-subprocess-local':
+ specifier: workspace:^
+ version: link:../../packages/subprocess/subprocess-local
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:^
version: link:../../packages/core/system-prompt
diff --git a/python/sdk-runtime/package.json b/python/sdk-runtime/package.json
index 6fca02ccf8..c2124da532 100644
--- a/python/sdk-runtime/package.json
+++ b/python/sdk-runtime/package.json
@@ -40,8 +40,8 @@
"@deepseek-ai/dsh-llm-pi-ai": "workspace:^",
"@deepseek-ai/dsh-llm-retry": "workspace:^",
"@deepseek-ai/dsh-plan-mode": "workspace:^",
- "@deepseek-ai/dsh-process": "workspace:^",
- "@deepseek-ai/dsh-process-local": "workspace:^",
+ "@deepseek-ai/dsh-subprocess": "workspace:^",
+ "@deepseek-ai/dsh-subprocess-local": "workspace:^",
"@deepseek-ai/dsh-permission": "workspace:^",
"@deepseek-ai/dsh-paths": "workspace:^",
"@deepseek-ai/dsh-repeat-tool-guard": "workspace:^",
diff --git a/python/sdk-runtime/src/deepseek_harness_runtime/runtime/cordis.yml b/python/sdk-runtime/src/deepseek_harness_runtime/runtime/cordis.yml
index b59b94a257..2f35e58d43 100644
--- a/python/sdk-runtime/src/deepseek_harness_runtime/runtime/cordis.yml
+++ b/python/sdk-runtime/src/deepseek_harness_runtime/runtime/cordis.yml
@@ -34,8 +34,8 @@
# Local bash executor; $DSH_CWD wins over the process cwd.
# Managed child-process groups for the bash executor (spawn/kill/output plumbing).
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
diff --git a/python/sdk/tests/test_bundled_runtime.py b/python/sdk/tests/test_bundled_runtime.py
index 755e65e00a..07f9b170ce 100644
--- a/python/sdk/tests/test_bundled_runtime.py
+++ b/python/sdk/tests/test_bundled_runtime.py
@@ -30,8 +30,8 @@ _CORDIS_YML = """\
root: './sessions'
- id: session-checkpoints
name: '@deepseek-ai/dsh-session-checkpoint-policy'
-- id: processes
- name: '@deepseek-ai/dsh-process-local'
+- id: subprocess
+ name: '@deepseek-ai/dsh-subprocess-local'
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts
index 07adb0ba0c..82e7b3f6d6 100644
--- a/scripts/gen-cordis-catalog.ts
+++ b/scripts/gen-cordis-catalog.ts
@@ -64,12 +64,12 @@ export const LINK_MAP: Record = {
BashExecSpec: 'bash.md',
BashProcess: 'bash.md',
BashRunResult: 'bash.md',
- DshEnvironment: 'process.md',
- ProcessHandle: 'process.md',
- ProcessOutcome: 'process.md',
- ProcessOutputRead: 'process.md',
- ProcessOutputReader: 'process.md',
- ProcessSpawnSpec: 'process.md',
+ DshEnvironment: 'subprocess.md',
+ SubprocessHandle: 'subprocess.md',
+ SubprocessOutcome: 'subprocess.md',
+ SubprocessOutputRead: 'subprocess.md',
+ SubprocessOutputReader: 'subprocess.md',
+ SubprocessSpawnSpec: 'subprocess.md',
CodeRunRequest: 'code-runtime.md',
CodeRunResult: 'code-runtime.md',
CompactionResult: 'compaction.md',
diff --git a/scripts/gen-doc-graphs.ts b/scripts/gen-doc-graphs.ts
index 05cec1dbad..6924cc2761 100644
--- a/scripts/gen-doc-graphs.ts
+++ b/scripts/gen-doc-graphs.ts
@@ -266,13 +266,13 @@ const SERVICE_ROLES: ServiceRole[] = [
note: 'Folds revisioned objective state from the session log and keeps live continuation activation process-local.',
},
{
- key: 'processes',
+ key: 'subprocess',
pkg: 'process',
title: 'Process manager seam',
mode: 'seam',
implementations: ['process-local'],
consumers: ['bash-local', 'bash-sandbox'],
- note: 'The bash executors spawn their process groups through ctx.processes; the manager owns group lifetime, bounded spill-backed output, and kill escalation.',
+ note: 'The bash executors spawn their process groups through ctx.subprocess; the service owns group lifetime, bounded spill-backed output, and kill escalation.',
},
{
key: 'bash',
diff --git a/scripts/gen-tool-catalog.ts b/scripts/gen-tool-catalog.ts
index 5d787814eb..fb8ccc17c5 100644
--- a/scripts/gen-tool-catalog.ts
+++ b/scripts/gen-tool-catalog.ts
@@ -19,7 +19,7 @@ import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools
import { BashExecutor } from '@deepseek-ai/dsh-bash'
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from '@deepseek-ai/dsh-bash'
import LocalBashExecutor from '@deepseek-ai/dsh-bash-local'
-import LocalProcessManager from '@deepseek-ai/dsh-process-local'
+import LocalSubprocessService from '@deepseek-ai/dsh-subprocess-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
import PlanModeService from '@deepseek-ai/dsh-plan-mode'
@@ -198,7 +198,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
requires: ['ctx.tools', 'ctx.bash', 'ctx.tasks at call time for run_in_background'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
- await ctx.plugin(LocalProcessManager)
+ await ctx.plugin(LocalSubprocessService)
await ctx.plugin(LocalBashExecutor)
await ctx.plugin(ToolBash)
},
diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json
index 629cfe4038..8dfde7ad11 100644
--- a/scripts/type-equiv.manifest.json
+++ b/scripts/type-equiv.manifest.json
@@ -2163,29 +2163,44 @@
"source": "packages/workflow/workflow/src/types.ts"
},
{
- "doc": "docs/core-data-structures/process.md",
- "symbol": "ProcessSpawnSpec",
- "source": "packages/process/process/src/types.ts"
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "SubprocessSpawnSpec",
+ "source": "packages/subprocess/subprocess/src/types.ts"
},
{
- "doc": "docs/core-data-structures/process.md",
- "symbol": "ProcessHandle",
- "source": "packages/process/process/src/types.ts"
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "SubprocessHandle",
+ "source": "packages/subprocess/subprocess/src/types.ts"
},
{
- "doc": "docs/core-data-structures/process.md",
- "symbol": "ProcessOutputReader",
- "source": "packages/process/process/src/types.ts"
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "SubprocessOutputReader",
+ "source": "packages/subprocess/subprocess/src/types.ts"
},
{
- "doc": "docs/core-data-structures/process.md",
- "symbol": "ProcessOutputRead",
- "source": "packages/process/process/src/types.ts"
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "SubprocessOutputRead",
+ "source": "packages/subprocess/subprocess/src/types.ts"
},
{
- "doc": "docs/core-data-structures/process.md",
- "symbol": "ProcessOutcome",
- "source": "packages/process/process/src/types.ts"
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "SubprocessOutcome",
+ "source": "packages/subprocess/subprocess/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "DshEnvironmentKey",
+ "source": "packages/subprocess/subprocess/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "DshEnvironment",
+ "source": "packages/subprocess/subprocess/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subprocess.md",
+ "symbol": "CollectedOutput",
+ "source": "packages/subprocess/subprocess/src/types.ts"
}
]
}
diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts
index dc284e9b21..0db4417fa5 100644
--- a/scripts/verify-package-readme-model-experience.ts
+++ b/scripts/verify-package-readme-model-experience.ts
@@ -72,8 +72,8 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = {
'packages/llm/token-meter': { kind: 'indirect', reason: 'The measurement service leaves model-visible changes to its consumers.' },
'packages/lsp/lsp': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-lsp.' },
'packages/lsp/lsp-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-lsp.' },
- 'packages/process/process': { kind: 'indirect', reason: 'The seam delegates all model rendering to consumer seams such as the bash executor family.' },
- 'packages/process/process-local': { kind: 'indirect', reason: 'The manager backend delegates model rendering to consumer seams such as the bash executor family.' },
+ 'packages/subprocess/subprocess': { kind: 'indirect', reason: 'The seam delegates all model rendering to consumer seams such as the bash executor family.' },
+ 'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
'packages/sandbox/sandbox-policy': { kind: 'indirect', reason: 'The policy service holds the mode dsh-tool-bash and dsh-tool-fs render in their denial markers.' },
'packages/sdk/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' },
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 2f73278c55..79c5ce1a83 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -54,7 +54,7 @@
"./packages/prompt/*/src/invariant.ts",
"./packages/llm/*/src/invariant.ts",
"./packages/bash/*/src/invariant.ts",
- "./packages/process/*/src/invariant.ts",
+ "./packages/subprocess/*/src/invariant.ts",
"./packages/code-runtime/*/src/invariant.ts",
"./packages/fs/*/src/invariant.ts",
"./packages/skill/*/src/invariant.ts",
@@ -124,7 +124,7 @@
"./packages/llm/*/src",
"./packages/bash/*/src",
"./packages/pty/*/src",
- "./packages/process/*/src",
+ "./packages/subprocess/*/src",
"./packages/code-runtime/*/src",
"./packages/fs/*/src",
"./packages/lsp/*/src",
diff --git a/tsconfig.host.json b/tsconfig.host.json
index c4bdc74fe1..a6397660a4 100644
--- a/tsconfig.host.json
+++ b/tsconfig.host.json
@@ -81,8 +81,8 @@
{ "path": "./packages/llm/llm-retry" },
{ "path": "./packages/examples/agent-spine-demo" },
{ "path": "./packages/examples/cli-demo" },
- { "path": "./packages/process/process" },
- { "path": "./packages/process/process-local" },
+ { "path": "./packages/subprocess/subprocess" },
+ { "path": "./packages/subprocess/subprocess-local" },
{ "path": "./packages/bash/bash" },
{ "path": "./packages/pty/pty" },
{ "path": "./packages/pty/pty-local" },
diff --git a/vitest.config.ts b/vitest.config.ts
index a0379a325b..48f872bcb9 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -11,7 +11,7 @@ const windowsUnsupportedPackages = process.platform === 'win32'
? [
'packages/bash/*',
'packages/hooks/*',
- 'packages/process/*',
+ 'packages/subprocess/*',
'packages/pty/pty-local',
'packages/sandbox/sandbox-local',
'packages/sdk/create-sdk',
@@ -41,7 +41,7 @@ const testIncludes = [
// that worker threads cannot isolate reliably under aggregate gate contention.
// Keep the narrow exception in forks while the rest of the inventory avoids per-file processes.
const processBoundTests = [
- 'packages/process/process-local/tests/spawn.spec.ts',
+ 'packages/subprocess/subprocess-local/tests/spawn.spec.ts',
'packages/context/time-context/tests/time-context.spec.ts',
'packages/llm/llm-pi-ai/tests/adapter.spec.ts',
'packages/ui/app-boot/tests/app-boot.spec.ts',