Merge branch 'worktree-subprocess-consumers' into worktree-process-service-seam
This commit is contained in:
@@ -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: 9c4c9fc12a51c9c49d02a7aa9c3633ae7c95e4fe
|
||||
architecture.zh.md: bc6adef969f60e7a7522a1877d29b2f90fc0c322
|
||||
architecture.md: 8334153482843f26defa8d175042a1349d6c9eca
|
||||
architecture.zh.md: bb874654496bc9d131b1d7529c3585ed4682c0cf
|
||||
@@ -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.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | managed child-process groups under the bash executors |
|
||||
| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | managed child-process trees for the bash executors, the LSP host, and the ACP subagent backend |
|
||||
| `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 |
|
||||
|
||||
@@ -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.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | bash 执行器之下受管理的子进程组 |
|
||||
| `ctx.subprocess` | [`subprocess/`](../packages/subprocess/README.md) | 供 bash 执行器、LSP host 与 ACP subagent 后端使用的受管子进程树 |
|
||||
| `ctx.pty` | [`pty/`](../packages/pty/README.md) | 按 owner 隔离的持久化终端会话 |
|
||||
| `ctx.sandbox` | [`sandbox/`](../packages/sandbox/README.md) | 同一执行环境内的进程限制(argv 包装、逐调用策略) |
|
||||
| `ctx.sandboxPolicy` | [`sandbox/`](../packages/sandbox/README.md) | 共享沙箱策略归属点 |
|
||||
|
||||
@@ -87,6 +87,8 @@ flowchart LR
|
||||
pkg_subprocess_local["subprocess-local"]
|
||||
pkg_bash_local["bash-local"]
|
||||
pkg_bash_sandbox["bash-sandbox"]
|
||||
pkg_lsp_local["lsp-local"]
|
||||
pkg_subagent_acp["subagent-acp"]
|
||||
pkg_bash["bash"]
|
||||
svc_bash["ctx.bash<br/>Bash executor seam"]
|
||||
svc_bashEnv["ctx.bashEnv<br/>Managed bash environment registry"]
|
||||
@@ -116,7 +118,6 @@ flowchart LR
|
||||
svc_subagents["ctx.subagents<br/>Subagent provider registry"]
|
||||
pkg_subagent_spawn["subagent-spawn"]
|
||||
pkg_subagent_fork["subagent-fork"]
|
||||
pkg_subagent_acp["subagent-acp"]
|
||||
pkg_tool_ralph["tool-ralph"]
|
||||
pkg_tasks["tasks"]
|
||||
svc_tasks["ctx.tasks<br/>Background task registry"]
|
||||
@@ -270,6 +271,8 @@ flowchart LR
|
||||
svc_subagents --> pkg_tool_subagent
|
||||
svc_subprocess --> pkg_bash_local
|
||||
svc_subprocess --> pkg_bash_sandbox
|
||||
svc_subprocess --> pkg_lsp_local
|
||||
svc_subprocess --> pkg_subagent_acp
|
||||
svc_systemPrompt --> pkg_agent_loop
|
||||
svc_systemPrompt --> pkg_tool_fs
|
||||
svc_systemPrompt --> pkg_tool_pty
|
||||
@@ -324,7 +327,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.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-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.subprocess` | `seam` | [`subprocess`](../packages/subprocess/subprocess) | [`subprocess-local`](../packages/subprocess/subprocess-local) | [`bash-local`](../packages/bash/bash-local), [`bash-sandbox`](../packages/bash/bash-sandbox), [`lsp-local`](../packages/lsp/lsp-local), [`subagent-acp`](../packages/subagent/subagent-acp) | - | The bash executors, the LSP host, and the ACP subagent backend spawn their children through ctx.subprocess; the service owns tree lifetime, stdio dispositions (pipes, inherit, bounded spill-backed collection), 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. |
|
||||
|
||||
@@ -716,7 +716,7 @@ Source: [`packages/llm/llm-retry/src/index.ts:39`](../packages/llm/llm-retry/src
|
||||
|
||||
## `@deepseek-ai/dsh-lsp-local`
|
||||
|
||||
Requires: `lsp`
|
||||
Requires: `lsp` · `subprocess`
|
||||
|
||||
```ts config-catalog
|
||||
/** Plugin configuration: provider id → local language-server configuration. */
|
||||
@@ -752,7 +752,7 @@ export interface LspLocalServerConfig {
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/lsp/lsp-local/src/index.ts:85`](../packages/lsp/lsp-local/src/index.ts)
|
||||
Source: [`packages/lsp/lsp-local/src/index.ts:87`](../packages/lsp/lsp-local/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-mcp-client`
|
||||
|
||||
@@ -1286,7 +1286,7 @@ Source: [`packages/storage/storage-sqlite/src/index.ts:24`](../packages/storage/
|
||||
|
||||
## `@deepseek-ai/dsh-subagent-acp`
|
||||
|
||||
Requires: `subagents`
|
||||
Requires: `subagents` · `subprocess`
|
||||
|
||||
```ts config-catalog
|
||||
/** Config: how to spawn and drive the child ACP agent process. */
|
||||
@@ -2119,6 +2119,5 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them.
|
||||
- `@deepseek-ai/dsh-scripts` ([`packages/sdk/scripts/src/index.ts`](../packages/sdk/scripts/src/index.ts))
|
||||
- `@deepseek-ai/dsh-session-title-llm` ([`packages/session-title/session-title-llm/src/index.ts`](../packages/session-title/session-title-llm/src/index.ts))
|
||||
- `@deepseek-ai/dsh-subagent-inprocess` ([`packages/subagent/subagent-inprocess/src/index.ts`](../packages/subagent/subagent-inprocess/src/index.ts))
|
||||
- `@deepseek-ai/dsh-subagent-subprocess` ([`packages/subagent/subagent-subprocess/src/index.ts`](../packages/subagent/subagent-subprocess/src/index.ts))
|
||||
- `@deepseek-ai/dsh-telemetry` ([`packages/sdk/telemetry/src/index.ts`](../packages/sdk/telemetry/src/index.ts))
|
||||
- `@deepseek-ai/dsh-timeout` ([`packages/util/timeout/src/index.ts`](../packages/util/timeout/src/index.ts))
|
||||
@@ -1565,24 +1565,24 @@ Abstract subprocess service. Subclass, implement spawn, and load the subclass as
|
||||
|
||||
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.
|
||||
- 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.kill signals without escalation, SubprocessHandle.terminate (and the spec's abort signal) escalates SIGTERM→grace→SIGKILL, and SubprocessHandle.dispose runs the cooperative EOF-first ladder — all tree-scoped on every platform.
|
||||
- Disposal of the service terminates 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).
|
||||
* @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
|
||||
```
|
||||
|
||||
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)
|
||||
Source: [`packages/subprocess/subprocess/src/index.ts:90`](../../packages/subprocess/subprocess/src/index.ts)
|
||||
|
||||
## `ctx.systemPrompt` — `SystemPrompt`
|
||||
|
||||
|
||||
@@ -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
|
||||
subprocess.md: 2481afe324b0cbd2404186ae79670da81038f8f5
|
||||
subprocess.zh.md: 291233c456f73a1ea80de19290a6fc71a1f22725
|
||||
subprocess.md: cdd4507c7d37f47ca243ddf38114f5aa6b6f3ad1
|
||||
subprocess.zh.md: 78325c3255c42ed591bbd98fdbdb4fdfcba48202
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
English | [中文](subprocess.zh.md)
|
||||
|
||||
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.
|
||||
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 and out-of-process backends — the [bash executor family](bash.md) (collect-mode batch output), the LSP host (piped protocol streams + a collected stderr tail), and the ACP subagent backend (piped protocol streams + inherited stderr). This seam owns the managed `DSH_*` environment namespace, the shared credential scrub (`scrubbedParentEnv`), and the `CollectedOutput` shape; [dsh-bash](../../packages/bash/bash) re-exports the vocabulary so bash consumers keep one import root.
|
||||
|
||||
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`.
|
||||
`DSH_*` variables are Harness-owned child-process facts; implementations discard ambient `DSH_*` names before merging the caller's snapshot, and each collected stream reports its truncation and spill-recovery state through `CollectedOutput`.
|
||||
|
||||
```ts type-equiv
|
||||
/** One environment key inside the managed {@link DSH_ENV_PREFIX} namespace. */
|
||||
@@ -32,83 +32,170 @@ interface CollectedOutput {
|
||||
}
|
||||
```
|
||||
|
||||
## The fully-explicit spawn spec
|
||||
## Node-shaped stdio dispositions
|
||||
|
||||
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.
|
||||
Each stream's disposition is explicit, chosen per consumer: raw pipes for protocol framing (LSP JSON-RPC, ACP ndjson), inherit for pass-through diagnostics, and collect mode for bounded batch output — with the spill file optional, so a diagnostic tail (a language server's stderr) buffers without leaving files behind.
|
||||
|
||||
```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
|
||||
* subprocess-service default — decides them (the `dsh-bash` request/spec split
|
||||
* is the owning template).
|
||||
* 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
|
||||
}
|
||||
```
|
||||
|
||||
## The fully-explicit spawn spec
|
||||
|
||||
The seam applies no defaults: every disposition, 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
|
||||
* disposition, limit, and directory is explicit, so the caller's own config —
|
||||
* not a hidden subprocess-service default — decides them (the `dsh-bash`
|
||||
* request/spec split is the owning template).
|
||||
*/
|
||||
interface SubprocessSpawnSpec {
|
||||
/** Executable and arguments; `argv[0]` is the program. Never shell-interpreted here. */
|
||||
argv: readonly string[]
|
||||
/** Working directory for the child. */
|
||||
cwd: string
|
||||
/** Stdout in-memory cap; overflow spills to disk (tail kept in memory). */
|
||||
stdoutMaxBytes: number
|
||||
/** Stderr in-memory cap; overflow spills to disk (tail kept in memory). */
|
||||
stderrMaxBytes: number
|
||||
/** Per-stream spill-file cap; larger streams retain only their in-memory tail. */
|
||||
maxSpillBytes: number
|
||||
/** Grace period for kill escalation and for inherited pipes after process exit. */
|
||||
/** Per-stream stdio dispositions. */
|
||||
stdio: SubprocessStdio
|
||||
/**
|
||||
* Grace period in milliseconds 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).
|
||||
*/
|
||||
graceMs: number
|
||||
/**
|
||||
* Abort signal — kills the process group when it fires. The caller owns
|
||||
* deadlines and cause classification; this seam only reacts to the abort.
|
||||
* 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.
|
||||
*/
|
||||
signal?: AbortSignal | undefined
|
||||
/**
|
||||
* Bytes to write to the child's stdin, then close it. Absent (or empty)
|
||||
* leaves stdin closed/empty.
|
||||
*/
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries merged after the implementation's credential
|
||||
* scrub. `DSH_*` names are rejected and belong in {@link dshEnv}.
|
||||
* Ordinary environment entries merged onto the implementation's scrubbed
|
||||
* parent base (see `scrubbedParentEnv`). `DSH_*` names are rejected and
|
||||
* belong in {@link dshEnv}; a deliberately forwarded credential-shaped
|
||||
* entry survives because this layer merges after the scrub.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/**
|
||||
* Harness-owned `DSH_*` variables for this execution. Implementations
|
||||
* discard ambient `DSH_*` entries before merging this snapshot, so an
|
||||
* unavailable current fact cannot inherit a stale value from the harness
|
||||
* process, and reject non-`DSH_*` names supplied through this channel.
|
||||
* Harness-owned `DSH_*` variables for this execution. The scrubbed base has
|
||||
* already discarded ambient `DSH_*` entries, so an unavailable current fact
|
||||
* cannot inherit a stale value from the harness process; non-`DSH_*` names
|
||||
* on this channel are rejected.
|
||||
*/
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## Handles and offset-based reads
|
||||
## Handles: streams, readers, and tree-scoped termination
|
||||
|
||||
A spawn returns a live handle immediately. Output readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; the consuming-cursor model the bash tool presents is consumer-owned state over these readers.
|
||||
A spawn returns a live handle immediately. Collect-mode readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; piped streams belong to the caller. Termination is tree-scoped on every platform: `kill(signal)` sends one signal Node-style, `terminate()` escalates SIGTERM→grace→SIGKILL, `waitForExit()` observes the whole tree, and `dispose(graces)` runs the cooperative stdin-EOF→SIGTERM→SIGKILL ladder out-of-process children need.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A live child process. `kill()` starts the group SIGTERM→grace→SIGKILL
|
||||
* escalation; buffered output remains readable after exit.
|
||||
* 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.
|
||||
*/
|
||||
interface SubprocessHandle {
|
||||
/** Process id (group leader); -1 when the spawn itself failed. */
|
||||
/** Process id (tree root); -1 when the spawn itself failed. */
|
||||
readonly pid: number
|
||||
/** Live stdout reader (also readable after exit). */
|
||||
readonly stdout: SubprocessOutputReader
|
||||
/** Live stderr reader (also readable after exit). */
|
||||
readonly stderr: SubprocessOutputReader
|
||||
/** Resolves when the process closes; rejects only for spawn-level failures. */
|
||||
/** 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. */
|
||||
readonly done: Promise<SubprocessOutcome>
|
||||
/** Begin SIGTERM→grace→SIGKILL on the process group. Idempotent. */
|
||||
kill(): void
|
||||
/**
|
||||
* Send one signal to the process tree, Node-style — no escalation, no
|
||||
* timers. A no-op after the outcome has settled (the pid may be reused).
|
||||
* @param signal - the signal to deliver (default `SIGTERM`; Windows
|
||||
* force-terminates the tree for any value).
|
||||
*/
|
||||
kill(signal?: NodeJS.Signals): void
|
||||
/**
|
||||
* Begin the SIGTERM → `graceMs` → SIGKILL escalation on the process tree
|
||||
* (Windows force-terminates immediately). Idempotent; also triggered by the
|
||||
* spec's abort signal.
|
||||
*/
|
||||
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>
|
||||
/**
|
||||
* Tear the child down to quiescence, resolving only after exit: close stdin
|
||||
* (when this handle owns a piped one) and allow cooperative flush for
|
||||
* `eofGraceMs`, then SIGTERM with a `graceMs` window (POSIX), then forced
|
||||
* tree termination with a final bounded `graceMs` wait.
|
||||
* @param graces - the ladder's two windows, from the consumer's Config.
|
||||
* @throws when the child still has not exited `graceMs` after the forced tier.
|
||||
*/
|
||||
dispose(graces: SubprocessDisposeGraces): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Cursor-free incremental access to one live output stream. Offsets are
|
||||
* Cursor-free incremental access to one collected output stream. Offsets are
|
||||
* whole-stream byte coordinates owned by the caller, so independent readers
|
||||
* cannot consume one another's output.
|
||||
* 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).
|
||||
*/
|
||||
interface SubprocessOutputReader {
|
||||
/**
|
||||
@@ -136,26 +223,62 @@ interface SubprocessOutputRead {
|
||||
}
|
||||
```
|
||||
|
||||
## Outcomes carry no cause classification
|
||||
|
||||
`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
|
||||
/** 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
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Raw outcome of one closed process. Deliberately carries NO timeout or
|
||||
* cancellation classification: the service kills on abort but does not decide
|
||||
* why — the caller reads the signal it owns to classify causes.
|
||||
* The two grace periods of the cooperative dispose ladder
|
||||
* ({@link SubprocessHandle.dispose}). Consumers carry them as defaulted,
|
||||
* validated Config fields, so teardown timing is deployment-tunable and this
|
||||
* seam hardcodes nothing.
|
||||
*/
|
||||
interface SubprocessDisposeGraces {
|
||||
/**
|
||||
* Tier-1 window (ms): after stdin EOF, how long the child gets to quiesce
|
||||
* ON ITS OWN — flush durable state, tear down its own descendants — before
|
||||
* escalation to platform termination. Usually WIDER than
|
||||
* {@link SubprocessDisposeGraces.graceMs}: a cooperative child's EOF-driven
|
||||
* teardown may itself wait on a signal-trapping grandchild plus a final
|
||||
* flush.
|
||||
*/
|
||||
eofGraceMs: number
|
||||
/**
|
||||
* Termination confirmation window (ms): POSIX applies it after `SIGTERM`
|
||||
* and again after `SIGKILL`; Windows applies it after the forced tree
|
||||
* termination.
|
||||
*/
|
||||
graceMs: number
|
||||
}
|
||||
```
|
||||
|
||||
## Outcomes carry exit facts only
|
||||
|
||||
`done` reports Node's close-event vocabulary and no cause classification — the service kills on abort but never decides why (the caller reads the deadline signal it owns, e.g. the bash executor's `timedOut`/`aborted` split). Collected output stays readable through `handle.collected` after settlement, so batch and streaming callers share one access path.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
stdout: CollectedOutput
|
||||
stderr: CollectedOutput
|
||||
}
|
||||
```
|
||||
|
||||
## Service behavior
|
||||
|
||||
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.
|
||||
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 trees, per-disposition wiring, credential scrub, terminate-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.
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
[English](subprocess.md) | 中文
|
||||
|
||||
进程管理器 seam 分为接口([dsh-subprocess](../../packages/subprocess/subprocess),`ctx.subprocess`)与实现([dsh-subprocess-local](../../packages/subprocess/subprocess-local));它的消费方是其他能力 seam:目前是 [bash 执行器家族](bash.md),后者传入 `['bash', '-c', command]` argv,并拥有每一项默认值。该 seam 拥有受管的 `DSH_*` 环境命名空间与 `CollectedOutput` 形状;[dsh-bash](../../packages/bash/bash) 将二者重导出,使 bash 消费方保持单一导入入口。
|
||||
进程管理器 seam 分为接口([dsh-subprocess](../../packages/subprocess/subprocess),`ctx.subprocess`)与实现([dsh-subprocess-local](../../packages/subprocess/subprocess-local));它的消费方是其他能力 seam 与进程外后端:[bash 执行器家族](bash.md)使用收集模式(collect)的批量输出,LSP 主机使用管道化的协议流 + 收集的 stderr 尾部,ACP(Agent Client Protocol)subagent 后端则使用管道化的协议流 + inherit 的 stderr。该 seam 拥有受管的 `DSH_*` 环境命名空间、共享的凭据清除(`scrubbedParentEnv`)与 `CollectedOutput` 形状;[dsh-bash](../../packages/bash/bash) 重导出这套词汇,使 bash 消费方保持单一导入入口。
|
||||
|
||||
源码:[`packages/subprocess/subprocess/src/types.ts`](../../packages/subprocess/subprocess/src/types.ts)
|
||||
|
||||
## 受管环境命名空间与捕获的输出
|
||||
|
||||
`DSH_*` 变量是归 Harness 所有的子进程事实;实现会在合并调用方快照之前丢弃环境中已有的 `DSH_*` 名称,每条被捕获的流都通过 `CollectedOutput` 报告自身的截断与 spill 恢复状态。
|
||||
`DSH_*` 变量是归 Harness 所有的子进程事实;实现会在合并调用方快照之前丢弃环境中已有的 `DSH_*` 名称,每条被收集的流都通过 `CollectedOutput` 报告自身的截断与 spill 恢复状态。
|
||||
|
||||
```ts type-equiv
|
||||
/** One environment key inside the managed {@link DSH_ENV_PREFIX} namespace. */
|
||||
@@ -32,83 +32,170 @@ interface CollectedOutput {
|
||||
}
|
||||
```
|
||||
|
||||
## 完全显式的 spawn spec
|
||||
## Node 形状的 stdio 处置方式(disposition)
|
||||
|
||||
该 seam 不应用任何默认值:每项限制与目录都在 spec 上显式给出,因此由调用方自己的配置决定它们,而不是由某个隐藏的进程管理器默认值决定。`argv` 绝不经过 shell 解释。
|
||||
每条流的处置方式都显式给出,由各消费方自行选择:原始管道用于协议分帧(LSP JSON-RPC、ACP ndjson),inherit 用于直通的诊断输出,收集模式用于有界的批量输出;其中 spill 文件是可选的,因此诊断尾部(语言服务器的 stderr)可以只在内存中缓冲,不留下任何文件。
|
||||
|
||||
```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
|
||||
* subprocess-service default — decides them (the `dsh-bash` request/spec split
|
||||
* is the owning template).
|
||||
* 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
|
||||
}
|
||||
```
|
||||
|
||||
## 完全显式的 spawn spec
|
||||
|
||||
该 seam 不应用任何默认值:每项处置方式、限制与目录都在 spec 上显式给出,因此由调用方自己的配置决定它们,而不是由某个隐藏的进程管理器默认值决定。`argv` 绝不经过 shell 解释。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A fully-specified spawn request. This seam applies no defaults: every
|
||||
* disposition, limit, and directory is explicit, so the caller's own config —
|
||||
* not a hidden subprocess-service default — decides them (the `dsh-bash`
|
||||
* request/spec split is the owning template).
|
||||
*/
|
||||
interface SubprocessSpawnSpec {
|
||||
/** Executable and arguments; `argv[0]` is the program. Never shell-interpreted here. */
|
||||
argv: readonly string[]
|
||||
/** Working directory for the child. */
|
||||
cwd: string
|
||||
/** Stdout in-memory cap; overflow spills to disk (tail kept in memory). */
|
||||
stdoutMaxBytes: number
|
||||
/** Stderr in-memory cap; overflow spills to disk (tail kept in memory). */
|
||||
stderrMaxBytes: number
|
||||
/** Per-stream spill-file cap; larger streams retain only their in-memory tail. */
|
||||
maxSpillBytes: number
|
||||
/** Grace period for kill escalation and for inherited pipes after process exit. */
|
||||
/** Per-stream stdio dispositions. */
|
||||
stdio: SubprocessStdio
|
||||
/**
|
||||
* Grace period in milliseconds 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).
|
||||
*/
|
||||
graceMs: number
|
||||
/**
|
||||
* Abort signal — kills the process group when it fires. The caller owns
|
||||
* deadlines and cause classification; this seam only reacts to the abort.
|
||||
* 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.
|
||||
*/
|
||||
signal?: AbortSignal | undefined
|
||||
/**
|
||||
* Bytes to write to the child's stdin, then close it. Absent (or empty)
|
||||
* leaves stdin closed/empty.
|
||||
*/
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries merged after the implementation's credential
|
||||
* scrub. `DSH_*` names are rejected and belong in {@link dshEnv}.
|
||||
* Ordinary environment entries merged onto the implementation's scrubbed
|
||||
* parent base (see `scrubbedParentEnv`). `DSH_*` names are rejected and
|
||||
* belong in {@link dshEnv}; a deliberately forwarded credential-shaped
|
||||
* entry survives because this layer merges after the scrub.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/**
|
||||
* Harness-owned `DSH_*` variables for this execution. Implementations
|
||||
* discard ambient `DSH_*` entries before merging this snapshot, so an
|
||||
* unavailable current fact cannot inherit a stale value from the harness
|
||||
* process, and reject non-`DSH_*` names supplied through this channel.
|
||||
* Harness-owned `DSH_*` variables for this execution. The scrubbed base has
|
||||
* already discarded ambient `DSH_*` entries, so an unavailable current fact
|
||||
* cannot inherit a stale value from the harness process; non-`DSH_*` names
|
||||
* on this channel are rejected.
|
||||
*/
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## 句柄与基于偏移量的读取
|
||||
## 句柄:流、读取器与以进程树为范围的终止
|
||||
|
||||
spawn 会立即返回一个实时句柄。输出读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;bash 工具呈现的消费游标模型,是消费方在这些读取器之上自行持有的状态。
|
||||
spawn 会立即返回一个实时句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`kill(signal)` 以 Node 风格只发送一个信号,`terminate()` 执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树,`dispose(graces)` 运行进程外子进程所需的协作式 stdin EOF→SIGTERM→SIGKILL 阶梯。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A live child process. `kill()` starts the group SIGTERM→grace→SIGKILL
|
||||
* escalation; buffered output remains readable after exit.
|
||||
* 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.
|
||||
*/
|
||||
interface SubprocessHandle {
|
||||
/** Process id (group leader); -1 when the spawn itself failed. */
|
||||
/** Process id (tree root); -1 when the spawn itself failed. */
|
||||
readonly pid: number
|
||||
/** Live stdout reader (also readable after exit). */
|
||||
readonly stdout: SubprocessOutputReader
|
||||
/** Live stderr reader (also readable after exit). */
|
||||
readonly stderr: SubprocessOutputReader
|
||||
/** Resolves when the process closes; rejects only for spawn-level failures. */
|
||||
/** 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. */
|
||||
readonly done: Promise<SubprocessOutcome>
|
||||
/** Begin SIGTERM→grace→SIGKILL on the process group. Idempotent. */
|
||||
kill(): void
|
||||
/**
|
||||
* Send one signal to the process tree, Node-style — no escalation, no
|
||||
* timers. A no-op after the outcome has settled (the pid may be reused).
|
||||
* @param signal - the signal to deliver (default `SIGTERM`; Windows
|
||||
* force-terminates the tree for any value).
|
||||
*/
|
||||
kill(signal?: NodeJS.Signals): void
|
||||
/**
|
||||
* Begin the SIGTERM → `graceMs` → SIGKILL escalation on the process tree
|
||||
* (Windows force-terminates immediately). Idempotent; also triggered by the
|
||||
* spec's abort signal.
|
||||
*/
|
||||
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>
|
||||
/**
|
||||
* Tear the child down to quiescence, resolving only after exit: close stdin
|
||||
* (when this handle owns a piped one) and allow cooperative flush for
|
||||
* `eofGraceMs`, then SIGTERM with a `graceMs` window (POSIX), then forced
|
||||
* tree termination with a final bounded `graceMs` wait.
|
||||
* @param graces - the ladder's two windows, from the consumer's Config.
|
||||
* @throws when the child still has not exited `graceMs` after the forced tier.
|
||||
*/
|
||||
dispose(graces: SubprocessDisposeGraces): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Cursor-free incremental access to one live output stream. Offsets are
|
||||
* Cursor-free incremental access to one collected output stream. Offsets are
|
||||
* whole-stream byte coordinates owned by the caller, so independent readers
|
||||
* cannot consume one another's output.
|
||||
* 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).
|
||||
*/
|
||||
interface SubprocessOutputReader {
|
||||
/**
|
||||
@@ -136,26 +223,62 @@ interface SubprocessOutputRead {
|
||||
}
|
||||
```
|
||||
|
||||
## 结果不携带原因分类
|
||||
|
||||
`done` 报告原始退出事实。服务会在中止时终止进程,但绝不判定原因:调用方读取归自己所有的 deadline 信号,以区分超时与取消(即 bash 执行器的 `timedOut`/`aborted` 拆分)。
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Raw outcome of one closed process. Deliberately carries NO timeout or
|
||||
* cancellation classification: the service kills on abort but does not decide
|
||||
* why — the caller reads the signal it owns to classify causes.
|
||||
* The two grace periods of the cooperative dispose ladder
|
||||
* ({@link SubprocessHandle.dispose}). Consumers carry them as defaulted,
|
||||
* validated Config fields, so teardown timing is deployment-tunable and this
|
||||
* seam hardcodes nothing.
|
||||
*/
|
||||
interface SubprocessDisposeGraces {
|
||||
/**
|
||||
* Tier-1 window (ms): after stdin EOF, how long the child gets to quiesce
|
||||
* ON ITS OWN — flush durable state, tear down its own descendants — before
|
||||
* escalation to platform termination. Usually WIDER than
|
||||
* {@link SubprocessDisposeGraces.graceMs}: a cooperative child's EOF-driven
|
||||
* teardown may itself wait on a signal-trapping grandchild plus a final
|
||||
* flush.
|
||||
*/
|
||||
eofGraceMs: number
|
||||
/**
|
||||
* Termination confirmation window (ms): POSIX applies it after `SIGTERM`
|
||||
* and again after `SIGKILL`; Windows applies it after the forced tree
|
||||
* termination.
|
||||
*/
|
||||
graceMs: number
|
||||
}
|
||||
```
|
||||
|
||||
## 结果只承载退出事实
|
||||
|
||||
`done` 报告 Node close 事件的词汇,不携带原因分类:服务会在中止时终止进程,但绝不判定原因(调用方读取归自己所有的 deadline 信号,例如 bash 执行器的 `timedOut`/`aborted` 拆分)。收集到的输出在结算后仍可经 `handle.collected` 读取,因此批量与流式调用方共用一条访问路径。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
stdout: CollectedOutput
|
||||
stderr: CollectedOutput
|
||||
}
|
||||
```
|
||||
|
||||
## 服务行为
|
||||
|
||||
抽象的 [`SubprocessService`](../../packages/subprocess/subprocess/src/index.ts) seam 只定义 `spawn`;[`LocalSubprocessService`](../../packages/subprocess/subprocess-local/src/index.ts) 是本地实现(detached 进程组、以 spill 文件兜底的尾部保留收集、凭据清除、先终止再等待退出的 dispose(资源释放))。seam 契约见 [`dsh-subprocess`](../../packages/subprocess/subprocess/README.md),具体机制见 [`dsh-subprocess-local`](../../packages/subprocess/subprocess-local/README.md)。
|
||||
抽象的 [`SubprocessService`](../../packages/subprocess/subprocess/src/index.ts) seam 只定义 `spawn`;[`LocalSubprocessService`](../../packages/subprocess/subprocess-local/src/index.ts) 是本地实现(detached 进程树、按处置方式接线的流、凭据清除、先终止再等待退出的 dispose(资源释放))。seam 契约见 [`dsh-subprocess`](../../packages/subprocess/subprocess/README.md),具体机制见 [`dsh-subprocess-local`](../../packages/subprocess/subprocess-local/README.md)。
|
||||
+12
-10
@@ -64,7 +64,6 @@ flowchart TD
|
||||
pkg_subagent_fork["subagent-fork"]
|
||||
pkg_subagent_inprocess["subagent-inprocess"]
|
||||
pkg_subagent_spawn["subagent-spawn"]
|
||||
pkg_subagent_subprocess["subagent-subprocess"]
|
||||
pkg_tool_subagent["tool-subagent"]
|
||||
end
|
||||
subgraph group_web["packages/web"]
|
||||
@@ -232,7 +231,6 @@ flowchart TD
|
||||
pkg_timeout --> pkg_invariants
|
||||
pkg_scope --> pkg_invariants
|
||||
pkg_skill --> pkg_invariants
|
||||
pkg_subagent_subprocess --> pkg_invariants
|
||||
pkg_acp_snapshot --> pkg_invariants
|
||||
pkg_llm_mock_server --> pkg_invariants
|
||||
pkg_loader_smoke --> pkg_invariants
|
||||
@@ -282,6 +280,7 @@ flowchart TD
|
||||
pkg_client_ui_workspace --> pkg_invariants
|
||||
pkg_helper --> pkg_brand
|
||||
pkg_helper --> pkg_invariants
|
||||
pkg_helper --> pkg_subprocess
|
||||
pkg_telemetry --> pkg_brand
|
||||
pkg_telemetry --> pkg_invariants
|
||||
pkg_telemetry --> pkg_paths
|
||||
@@ -293,6 +292,7 @@ flowchart TD
|
||||
pkg_storage_sqlite --> pkg_storage
|
||||
pkg_subprocess_local --> pkg_invariants
|
||||
pkg_subprocess_local --> pkg_subprocess
|
||||
pkg_subprocess_local --> pkg_timeout
|
||||
pkg_llm_deepseek --> pkg_invariants
|
||||
pkg_llm_deepseek --> pkg_llm
|
||||
pkg_llm_deepseek --> pkg_timeout
|
||||
@@ -383,6 +383,7 @@ flowchart TD
|
||||
pkg_lsp_local --> pkg_invariants
|
||||
pkg_lsp_local --> pkg_llm
|
||||
pkg_lsp_local --> pkg_lsp
|
||||
pkg_lsp_local --> pkg_subprocess
|
||||
pkg_lsp_local --> pkg_timeout
|
||||
pkg_sandbox_local --> pkg_invariants
|
||||
pkg_sandbox_local --> pkg_llm
|
||||
@@ -541,6 +542,7 @@ flowchart TD
|
||||
pkg_pty_local --> pkg_sandbox
|
||||
pkg_pty_local --> pkg_sandbox_policy
|
||||
pkg_pty_local --> pkg_session
|
||||
pkg_pty_local --> pkg_subprocess
|
||||
pkg_tasks_local --> pkg_agent
|
||||
pkg_tasks_local --> pkg_invariants
|
||||
pkg_tasks_local --> pkg_tasks
|
||||
@@ -678,6 +680,7 @@ flowchart TD
|
||||
pkg_tool_lsp --> pkg_tools
|
||||
pkg_mcp_client --> pkg_invariants
|
||||
pkg_mcp_client --> pkg_llm
|
||||
pkg_mcp_client --> pkg_subprocess
|
||||
pkg_mcp_client --> pkg_tools
|
||||
pkg_tool_pty --> pkg_agent
|
||||
pkg_tool_pty --> pkg_invariants
|
||||
@@ -704,7 +707,7 @@ flowchart TD
|
||||
pkg_subagent_acp --> pkg_llm
|
||||
pkg_subagent_acp --> pkg_session
|
||||
pkg_subagent_acp --> pkg_subagent
|
||||
pkg_subagent_acp --> pkg_subagent_subprocess
|
||||
pkg_subagent_acp --> pkg_subprocess
|
||||
pkg_subagent_inprocess --> pkg_agent
|
||||
pkg_subagent_inprocess --> pkg_invariants
|
||||
pkg_subagent_inprocess --> pkg_llm
|
||||
@@ -843,7 +846,6 @@ flowchart TD
|
||||
| [`timeout`](../packages/util/timeout) | `util` | [`invariants`](../packages/support/invariants) |
|
||||
| [`scope`](../packages/core/scope) | `core` | [`invariants`](../packages/support/invariants) |
|
||||
| [`skill`](../packages/skill/skill) | `skill` | [`invariants`](../packages/support/invariants) |
|
||||
| [`subagent-subprocess`](../packages/subagent/subagent-subprocess) | `subagent` | [`invariants`](../packages/support/invariants) |
|
||||
| [`acp-snapshot`](../packages/support/acp-snapshot) | `support` | [`invariants`](../packages/support/invariants) |
|
||||
| [`llm-mock-server`](../packages/support/llm-mock-server) | `support` | [`invariants`](../packages/support/invariants) |
|
||||
| [`loader-smoke`](../packages/support/loader-smoke) | `support` | [`invariants`](../packages/support/invariants) |
|
||||
@@ -870,12 +872,12 @@ flowchart TD
|
||||
| [`client-ui-settings`](../packages/client/ui-settings) | `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-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) |
|
||||
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants) |
|
||||
| [`helper`](../packages/sdk/helper) | `sdk` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess) |
|
||||
| [`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) |
|
||||
| [`subprocess-local`](../packages/subprocess/subprocess-local) | `subprocess` | [`invariants`](../packages/support/invariants), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
|
||||
| [`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) |
|
||||
@@ -902,7 +904,7 @@ flowchart TD
|
||||
| [`app-boot`](../packages/ui/app-boot) | `ui` | [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`system-prompt`](../packages/core/system-prompt) |
|
||||
| [`client-ui-layout`](../packages/client/ui-layout) | `client` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-theme`](../packages/client/ui-theme), [`invariants`](../packages/support/invariants) |
|
||||
| [`code-runtime-worker`](../packages/code-runtime/code-runtime-worker) | `code-runtime` | [`code-runtime`](../packages/code-runtime/code-runtime), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) |
|
||||
| [`lsp-local`](../packages/lsp/lsp-local) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`timeout`](../packages/util/timeout) |
|
||||
| [`lsp-local`](../packages/lsp/lsp-local) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`subprocess`](../packages/subprocess/subprocess), [`timeout`](../packages/util/timeout) |
|
||||
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox) |
|
||||
| [`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) |
|
||||
@@ -938,7 +940,7 @@ flowchart TD
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) |
|
||||
| [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) |
|
||||
| [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session) |
|
||||
| [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess) |
|
||||
| [`tasks-local`](../packages/tasks/tasks-local) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tasks`](../packages/tasks/tasks), [`timeout`](../packages/util/timeout) |
|
||||
| [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-goal`](../packages/goal/tool-goal) | `goal` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
@@ -961,11 +963,11 @@ flowchart TD
|
||||
| [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) |
|
||||
| [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) |
|
||||
| [`tool-lsp`](../packages/lsp/tool-lsp) | `lsp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`lsp`](../packages/lsp/lsp), [`system-prompt`](../packages/core/system-prompt), [`timeout`](../packages/util/timeout), [`tools`](../packages/core/tools) |
|
||||
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`tools`](../packages/core/tools) |
|
||||
| [`mcp-client`](../packages/mcp/mcp-client) | `mcp` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools) |
|
||||
| [`tool-pty`](../packages/pty/tool-pty) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`pty`](../packages/pty/pty), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-tasks`](../packages/tasks/tool-tasks) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`retention`](../packages/util/retention), [`system-prompt`](../packages/core/system-prompt), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`tool-workflow`](../packages/workflow/tool-workflow) | `workflow` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-subprocess`](../packages/subagent/subagent-subprocess) |
|
||||
| [`subagent-acp`](../packages/subagent/subagent-acp) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) |
|
||||
| [`subagent-inprocess`](../packages/subagent/subagent-inprocess) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) |
|
||||
| [`tool-subagent`](../packages/subagent/tool-subagent) | `subagent` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`tasks`](../packages/tasks/tasks), [`tools`](../packages/core/tools) |
|
||||
| [`hooks-claude`](../packages/hooks/hooks-claude) | `hooks` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/hooks/hook-protocol), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) |
|
||||
|
||||
Reference in New Issue
Block a user