fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
# 子进程
2026-07-26 22:00:38 +08:00
[English ](subprocess.md ) | 中文
2026-08-18 19:00:37 +08:00
子进程 seam 分为 Service Definition( [dsh-subprocess ](../../packages/subprocess/subprocess ), `ctx.subprocess` )与 Service Provider( [dsh-subprocess-local ](../../packages/subprocess/subprocess-local ));它的 Consumer 是其他能力 seam 与进程外后端:[bash 执行器家族 ](shell.zh.md )使用收集模式的批量输出, LSP 使用原始协议管道, PTY 后端使用终端原语, ACP( Agent Client Protocol) subagent 后端则使用通过管道传输的 ndjson, 并让 stderr 采用 inherit。该 seam 拥有受管的 `DSH_*` 环境命名空间、共享的凭据清除(`scrubbedParentEnv` )与 `CollectedOutput` 形状;[dsh-shell ](../../packages/shell/shell ) 重导出这套词汇,使 bash 消费方保持单一导入入口。
2026-07-26 22:00:38 +08:00
2026-07-28 23:00:00 +08:00
源码:[`packages/subprocess/subprocess/src/types.ts` ](../../packages/subprocess/subprocess/src/types.ts ) 与 [`packages/subprocess/subprocess/src/index.ts` ](../../packages/subprocess/subprocess/src/index.ts )
2026-07-30 14:24:47 +08:00
## 可执行文件查找
2026-07-28 23:00:00 +08:00
2026-07-30 14:24:47 +08:00
一个提供方的 spawn 工作目录、可执行文件路径、普通进程与终端会话,和挂载的文件系统提供方处于同一路径与进程命名空间。`resolveExecutable(command, env?, signal?)` 验证绝对可执行文件路径,或通过提供方清理后的 `PATH` 加有意覆盖来解析裸名称。
2026-07-26 22:00:38 +08:00
## 受管环境命名空间与捕获的输出
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
`DSH_*` 变量是归 Harness 所有的子进程事实;实现会在合并调用方显式 `env` 之前丢弃环境中已有的 `DSH_*` 名称,因此当前事实只会以有意提供的字符串条目形式到达,而显式的 `undefined` tombstone 会删除普通环境中已有的值。每条被收集的流都通过 `CollectedOutput` 报告自身的截断与 spill 恢复状态。
2026-07-26 22:00:38 +08:00
```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< Record < DshEnvironmentKey , string > >
```
```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
}
```
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
## Node 风格的 stdio 处置方式( disposition)
2026-07-26 22:54:44 +08:00
每条流的处置方式都显式给出, 由各消费方自行选择: 原始管道用于协议分帧( LSP JSON-RPC、ACP ndjson) , inherit 用于直通的诊断输出,收集模式用于有界的批量输出;其中 spill 文件是可选的,因此诊断尾部(语言服务器的 stderr) 可以只在内存中缓冲, 不留下任何文件。
```ts type-equiv
/**
* stdin disposition. `'ignore'` leaves fd 0 on `/dev/null` ; `'pipe'` exposes
* {@link SubprocessHandle.stdin} for the caller's ongoing protocol writes;
* `{ data }` writes the bytes and closes (the batch shape).
*/
type SubprocessStdinMode = 'ignore' | 'pipe' | { readonly data: string }
```
```ts type-equiv
/**
* Bounded in-memory collection for one output stream, with an optional
* full-stream spill file. Omitting `spill` keeps only the in-memory tail —
* the diagnostic-tail shape (a language server's stderr); including it makes
* the complete stream recoverable up to its cap (the bash tool shape).
*/
interface SubprocessCollect {
/** In-memory cap in bytes; overflow keeps the TAIL. */
maxBytes: number
/** Full-stream spill file; absent disables spilling entirely. */
spill?: {
/** Whole-stream byte cap; a larger stream discards its now-incomplete spill. */
maxBytes: number
}
}
```
```ts type-equiv
/**
* stdout/stderr disposition. `'pipe'` exposes the raw `Readable` for the
* caller's protocol decoding; `'inherit'` passes the parent's descriptor
* through (child diagnostics land on the harness's own stream); a
* {@link SubprocessCollect} object buffers boundedly with offset-based reads.
*/
type SubprocessOutputMode = 'pipe' | 'inherit' | SubprocessCollect
```
```ts type-equiv
/** Per-stream stdio dispositions, all explicit — this seam applies no defaults. */
interface SubprocessStdio {
stdin: SubprocessStdinMode
stdout: SubprocessOutputMode
stderr: SubprocessOutputMode
}
```
2026-07-26 22:00:38 +08:00
## 完全显式的 spawn spec
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
该 seam 不应用任何默认值:每项处置方式、限制与目录都在 spec 上显式给出,因此由调用方自己的配置决定它们,而不是由某个隐藏的子进程服务默认值决定。`argv` 绝不经过 shell 解释。
2026-07-26 22:00:38 +08:00
```ts type-equiv
/**
2026-07-26 22:54:44 +08:00
* A fully-specified spawn request. This seam applies no defaults: every
* disposition, limit, and directory is explicit, so the caller's own config —
2026-08-13 00:36:22 +08:00
* not a hidden subprocess-service default — decides them (the `dsh-shell`
2026-07-26 22:54:44 +08:00
* request/spec split is the owning template).
2026-07-26 22:00:38 +08:00
*/
interface SubprocessSpawnSpec {
/** Executable and arguments; `argv[0]` is the program. Never shell-interpreted here. */
argv: readonly string[]
/** Working directory for the child. */
cwd: string
2026-07-26 22:54:44 +08:00
/** Per-stream stdio dispositions. */
stdio: SubprocessStdio
2026-07-26 22:00:38 +08:00
/**
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
* Positive finite grace period in milliseconds, no greater than
* `MAX_TIMER_DELAY_MS` , for the {@link SubprocessHandle.terminate} escalation
* and for draining still-open collected pipes after the process exits (an
* inherited descriptor held by a surviving descendant cannot hold the
* outcome open indefinitely).
2026-07-26 22:00:38 +08:00
*/
2026-07-26 22:54:44 +08:00
graceMs: number
2026-07-26 22:00:38 +08:00
/**
2026-07-26 22:54:44 +08:00
* Abort signal — starts the terminate escalation on the process tree when
* it fires. The caller owns deadlines and cause classification; this seam
* only reacts to the abort.
2026-07-26 22:00:38 +08:00
*/
2026-07-26 22:54:44 +08:00
signal?: AbortSignal | undefined
2026-07-26 22:00:38 +08:00
/**
2026-07-27 04:14:51 +08:00
* Explicit environment entries merged onto the implementation's scrubbed
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
* parent base (see `scrubbedParentEnv` ), with no namespace validation. A
* string is a deliberate caller opt-in, so a forwarded credential-shaped
* entry or current `DSH_*` fact survives the scrub; `undefined` is a
* tombstone that removes an ordinary ambient entry from the child.
2026-07-26 22:00:38 +08:00
*/
fix(rebase): migrate the replayed stack onto current master APIs
The linear replay carried each commit's own lineage, so this checkpoint
restores the master-owned surfaces the conflicted regions clobbered and
migrates branch-owned code to master's post-rebase APIs:
- rebuild subprocess-local spawn.ts on master's tree-exit-observer
machinery, keeping the branch's win32 childEnv key semantics and the
Linux zombie-quiescence probe; the zombie test reaps its survivor
directly since a confirmed-absent verdict is a permanent
no-more-signals boundary
- migrate pty-local test stubs to the Inbox-model Agent interface,
Session.create, runnerFailureRules, and the new turn/start payload
- implement the seam's resolveExecutable/spawnTerminal abstracts in the
new pwsh-local and tool-fs-search test fakes
- restore code-runtime, atomic-write, pwsh-local, and app-boot to
master's exact content (the net-zero code-runtime churn is pruned
from this history) and drop rename-detection graft debris
- re-apply the PR's architecture rows and execution-world paragraph,
re-record bilingual pairings, regenerate catalogs, and reconcile the
lockfile
2026-08-07 20:34:31 +08:00
env?: NodeJS.ProcessEnv | undefined
2026-07-26 22:00:38 +08:00
}
```
2026-07-26 22:54:44 +08:00
## 句柄:流、读取器与以进程树为范围的终止
2026-07-26 22:00:38 +08:00
2026-08-12 12:30:19 +08:00
spawn 会立即返回一个活动句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`terminate()` (唯一的终止动词)执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树。这足以让消费方构建自己的分级清理流程; ACP 后端的 `disposeAcpChild` 会先关闭 stdin, 让子进程收到 EOF, 是仓库内的参考实现。
2026-07-26 22:00:38 +08:00
```ts type-equiv
/**
2026-07-26 22:54:44 +08:00
* A live child process rooted in its own process tree. Collected output
* remains readable after exit; piped streams belong to the caller.
*
* Termination is tree-scoped everywhere: POSIX signals the detached process
* group (falling back to the direct child when the group is gone), Windows
* terminates the tree via `taskkill /T` , so helper processes cannot outlive
* the handle unnoticed.
2026-07-26 22:00:38 +08:00
*/
interface SubprocessHandle {
2026-07-26 22:54:44 +08:00
/** Process id (tree root); -1 when the spawn itself failed. */
2026-07-26 22:00:38 +08:00
readonly pid: number
2026-07-26 22:54:44 +08:00
/** The child's stdin, present iff spawned with `stdin: 'pipe'` . */
readonly stdin: Writable | undefined
/** The child's raw stdout, present iff spawned with `stdout: 'pipe'` . */
readonly stdout: Readable | undefined
/** The child's raw stderr, present iff spawned with `stderr: 'pipe'` . */
readonly stderr: Readable | undefined
/** Offset-based readers for collect-mode streams (also readable after exit). */
readonly collected: SubprocessCollectedOutputs
/** Resolves at process close with exit facts; rejects only for spawn-level failures. */
2026-07-26 22:00:38 +08:00
readonly done: Promise< SubprocessOutcome >
2026-07-26 22:54:44 +08:00
/**
* Begin the SIGTERM → `graceMs` → SIGKILL escalation on the process tree
2026-07-27 04:41:04 +08:00
* (Windows force-terminates immediately) — the seam's only termination
* verb. Idempotent, a no-op once the tree is gone (the pid may be reused),
* and also triggered by the spec's abort signal.
2026-07-26 22:54:44 +08:00
*/
terminate(): void
/**
* Wait until the process tree has exited — the tree, not just the direct
* child, so a still-running helper is observable before teardown returns.
* @param signal - optional bound for the wait.
* @returns `true` when the tree exited, `false` when the signal aborted first.
*/
waitForExit(signal?: AbortSignal): Promise< boolean >
2026-07-26 22:00:38 +08:00
}
```
```ts type-equiv
/**
2026-07-26 22:54:44 +08:00
* Cursor-free incremental access to one collected output stream. Offsets are
2026-07-26 22:00:38 +08:00
* whole-stream byte coordinates owned by the caller, so independent readers
2026-07-26 22:54:44 +08:00
* cannot consume one another's output; `readFrom(0)` after settlement is the
* batch result (`lossy` then means the in-memory tail lost its head — the
* {@link CollectedOutput.truncated} fact).
2026-07-26 22:00:38 +08:00
*/
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
* retained tail and the gap is only recoverable from the spill file.
* @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): SubprocessOutputRead
}
```
```ts type-equiv
/** 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. */
nextOffset: number
/** True when the requested offset slid out of the in-memory tail window. */
lossy: boolean
/** Path to the full-stream spill file, when one was created and remains intact. */
spillPath?: string
}
```
2026-07-26 22:54:44 +08:00
```ts type-equiv
/** Offset-based readers for the streams spawned in collect mode. */
interface SubprocessCollectedOutputs {
/** Present iff stdout is a {@link SubprocessCollect}. */
readonly stdout?: SubprocessOutputReader
/** Present iff stderr is a {@link SubprocessCollect}. */
readonly stderr?: SubprocessOutputReader
}
```
## 结果只承载退出事实
2026-07-26 22:00:38 +08:00
2026-07-26 22:54:44 +08:00
`done` 报告 Node close 事件的词汇,不携带原因分类:服务会在中止时终止进程,但绝不判定原因(调用方读取归自己所有的 deadline 信号,例如 bash 执行器的 `timedOut` /`aborted` 拆分)。收集到的输出在结算后仍可经 `handle.collected` 读取,因此批量与流式调用方共用一条访问路径。
2026-07-26 22:00:38 +08:00
```ts type-equiv
/**
2026-07-26 22:54:44 +08:00
* Exit facts of one closed process — Node's `close` -event vocabulary.
* Deliberately carries NO timeout or cancellation classification (the caller
* reads the signal it owns to classify causes) and NO output: collected
* streams stay readable through {@link SubprocessHandle.collected} after
* settlement, so batch and streaming callers share one access path.
2026-07-26 22:00:38 +08:00
*/
interface SubprocessOutcome {
/** Exit code; null when the process died from a signal. */
exitCode: number | null
/** Terminating signal (e.g. 'SIGTERM'); null on normal exit. */
signal: NodeJS.Signals | null
}
```
2026-07-28 23:00:00 +08:00
## 终端进程原语
2026-07-29 23:11:49 +08:00
`spawnTerminal(spec)` 是非管道进程原语。提供方分配控制终端,并负责 UTF-8 文本传输、前台进程组检查与信号发送,以及一项须等待的 TERM→KILL 操作; 该操作会使提供方仍可观察到的每个会话成员完全停稳, 提供方则会记录执行基底特有的可观察性限制。PTY 后端仍负责提示符检测、就绪推断、scrollback、沙箱策略和持久会话所有权; 普通 `spawn()` 无法重建控制终端语义。
2026-07-28 23:00:00 +08:00
2026-08-13 00:36:22 +08:00
终端 spec 完全指定 argv、cwd、环境覆盖、尺寸、清理宽限期与可选的分配取消。其句柄公开 `pid` 、有序输出、`done` 、`write` 、`inspectForeground` 、`signalForeground` 和须等待的 `terminate` ;确切的公共形状生成到 [`ctx.subprocess` 服务目录 ](#ctxsubprocess--subprocessruntime-abstract-seam )中。
2026-07-28 23:00:00 +08:00
2026-07-26 22:00:38 +08:00
## 服务行为
2026-08-18 19:00:37 +08:00
抽象的 [`SubprocessRuntime` ](../../packages/subprocess/subprocess/src/index.ts ) Service Definition 规定执行世界坐标、可执行文件查找、普通 `spawn` 与 `spawnTerminal` 。[`LocalSubprocessRuntime` ](../../packages/subprocess/subprocess-local/src/index.ts ) 以 detached 进程树、按处置方式接线、凭据清除、`node-pty` 、平台进程检查, 以及先终止再等待退出的资源释放提供这些能力。Service Definition 约定见 [`dsh-subprocess` ](../../packages/subprocess/subprocess/README.zh.md ),本地机制见 [`dsh-subprocess-local` ](../../packages/subprocess/subprocess-local/README.zh.md )。
2026-07-30 21:40:58 +08:00
<!-- BEGIN GENERATED cordis - surface (gen - cordis - catalog.ts) — do not edit between markers -->
< a id = "cordis-surface" > < / a >
2026-07-24 19:54:25 +08:00
## Cordis API
2026-07-30 21:40:58 +08:00
2026-08-18 21:02:50 +08:00
Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog` ) — the language sides differ only in locale-specific paired document paths. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer ](../cordis-primer.zh.md#dispatch-modes ), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md ](../cordis-api/inherited.md ).
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
< a id = "ctxe2b--e2bruntime" > < / a >
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
### `ctx.e2b` — `E2BRuntime`
2026-07-30 21:40:58 +08:00
Creates one lazily consumable E2B SDK handle and deletes the sandbox at timeout or disposal. Creation begins at plugin construction; adapters await getSandbox before their first operation.
```ts cordis-catalog
/**
* Return the shared live SDK handle.
* @returns the created sandbox after the configured cwd exists.
* @throws when E2B rejects creation or the service is disposing.
*/
async getSandbox(): Promise< Sandbox >
```
2026-08-04 11:38:42 +08:00
Source: [`packages/e2b/e2b/src/index.ts` ](../../packages/e2b/e2b/src/index.ts )
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
< a id = "ctxsubprocess--subprocessruntime-abstract-seam" > < / a >
2026-07-30 21:40:58 +08:00
2026-08-13 00:36:22 +08:00
### `ctx.subprocess` — `SubprocessRuntime` (abstract seam)
2026-07-30 21:40:58 +08:00
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:
- Executable paths belong to one execution world shared with the mounted filesystem provider.
- spawn returns immediately with a live handle; `done` resolves at process close with exit facts and rejects only for spawn-level failures.
- Collect-mode 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. Piped streams are handed to the caller raw and never buffered here.
- SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every platform. SubprocessHandle.waitForExit observes whole-tree liveness, so a consumer-owned teardown ladder can hold each tier on real quiescence.
- Disposal of the service terminates all still-running managed processes and awaits their exit.
- spawnTerminal owns terminal allocation, text transport, foreground groups, signalling, and whole-session quiescence behind one awaited termination method; readiness and persistent-shell policy stay in the PTY consumer. Its output stream ends after queued terminal output when the top-level process exits.
```ts cordis-catalog
/**
* Resolve one configured executable in this provider's execution world.
* Absolute paths are verified; bare names use the provider's scrubbed PATH
* plus explicit environment overrides. Relative paths containing separators
docs: purge chain-of-thought leakage from prose
Delete design-session citations (decision/audit/plan ordinals, stack
positions), change narration, review choreography, and reviewer-addressed
justification from comments, JSDoc, docs, READMEs, Agent Notes, tests, and
generator templates; restate every affected fact as current-state contract
prose. Fix generated docs at their sources and regenerate the catalogs and
cordis-surface regions; re-paste type-equiv blocks; update every bilingual
counterpart and re-record the pairs. Record the citation rule in the
committed-artifact-citations Agent Note.
2026-08-09 15:09:19 +08:00
* are rejected: the resolution base is undefined, so providers fail loud
* instead of guessing.
2026-07-30 21:40:58 +08:00
* @param command - absolute executable path or bare PATH name.
* @param env - explicit environment entries used for lookup.
* @param signal - aborts remote or local lookup.
* @returns a canonical executable path.
*/
abstract resolveExecutable( command: string, env?: Readonly< Record < string , string > >, signal?: AbortSignal, ): Promise< string >
/**
* Start one managed child process from a fully-specified spec; this seam
* applies no defaults.
* @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment.
* @returns the live process handle (streams/readers, signalling, outcome promise).
*/
abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle
/**
* Allocate a real terminal and start one owned process session. This is the
* only non-pipe process primitive: implementations own terminal byte I/O,
* foreground groups, signals, and complete session-tree cleanup.
* @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation.
* @returns the live terminal handle after allocation succeeds.
*/
abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise< SubprocessTerminalHandle >
```
2026-08-04 11:38:42 +08:00
Source: [`packages/subprocess/subprocess/src/index.ts` ](../../packages/subprocess/subprocess/src/index.ts )
2026-07-30 21:40:58 +08:00
<!-- END GENERATED cordis - surface -->