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',