mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into feature/subagent-policy-inheritance
# Conflicts: # docs/module-graph.md
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
|
||||
bash.md: 35cf2061588907dde41123efb01e453eb9cc929d
|
||||
bash.zh.md: 0cfeb9e1a858f7057e720215c41a757588751122
|
||||
bash.md: 3747244662301a256e12037ea67c21017b5ac2c5
|
||||
bash.zh.md: 9927aa8d51ee410d70bed7a2d00e40061b499e15
|
||||
|
||||
@@ -2,23 +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.
|
||||
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`; executors remove inherited `DSH_*` names before merging the current snapshot.
|
||||
|
||||
```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 bash execution. */
|
||||
type DshEnvironment = Readonly<Record<DshEnvironmentKey, string>>
|
||||
```
|
||||
`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
|
||||
|
||||
@@ -56,17 +46,18 @@ interface BashExecRequest {
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries for the command, merged after the credential
|
||||
* scrub. `DSH_*` is reserved for {@link dshEnv} and implementations reject it
|
||||
* here. Set by in-process plugins (the hooks bridges set
|
||||
* `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the model-facing bash tool
|
||||
* does not expose it as a parameter.
|
||||
* scrub. Managed facts belong in {@link dshEnv}, which merges after this
|
||||
* map, so an entry here can never displace one. Set by in-process plugins
|
||||
* (the hooks bridges set `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the
|
||||
* model-facing bash tool does not expose it as a parameter.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/**
|
||||
* Harness-owned `DSH_*` variables for this execution. Executors 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 managed channel.
|
||||
* Harness-owned `DSH_*` variables for this execution (typed to managed
|
||||
* keys). Executors discard ambient `DSH_*` entries before merging this
|
||||
* snapshot last, so an unavailable current fact cannot inherit a stale
|
||||
* value from the harness process and a caller {@link env} entry cannot
|
||||
* displace a managed one.
|
||||
*/
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Fully resolved per-call sandbox policy; sandboxing executors default it. */
|
||||
@@ -95,12 +86,12 @@ interface BashExecSpec {
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries carried through from
|
||||
* {@link BashExecRequest.env}. `DSH_*` remains reserved for {@link dshEnv}.
|
||||
* {@link BashExecRequest.env}; {@link dshEnv} still merges after them.
|
||||
* OPTIONAL on the spec for the same reason as `stdin`: absent means no
|
||||
* ordinary extra environment.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/** Managed `DSH_*` snapshot; implementations reject ordinary names. */
|
||||
/** Managed `DSH_*` snapshot (typed to managed keys); merges after {@link env}. */
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Resolved sandbox policy; ignored by executors that do not confine. */
|
||||
sandboxPolicy: SandboxExecutionPolicy | undefined
|
||||
@@ -145,19 +136,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:
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
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`
|
||||
|
||||
@@ -192,8 +171,9 @@ One more piece completes the vocabulary: the `SANDBOX_UNAVAILABLE` error code (o
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A background process handle returned by {@link BashExecutor.start}. It is the
|
||||
* only access path; buffered output remains readable after exit. Executor
|
||||
* disposal kills running processes and awaits {@link done}.
|
||||
* only access path; buffered output remains readable after exit. Composition
|
||||
* teardown (the subprocess service's disposal) kills running processes and
|
||||
* awaits {@link done}; an executor-only reload leaves them running.
|
||||
*/
|
||||
interface BashProcess {
|
||||
/** Process lifecycle state (settled exactly once). */
|
||||
@@ -238,4 +218,4 @@ interface BashProcessRead {
|
||||
|
||||
## The service
|
||||
|
||||
`BashExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns process groups, timeout/abort handling, bounded collectors, spill files, credential scrubbing, and disposal quiescence. `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).
|
||||
|
||||
@@ -2,23 +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 返回一个不含任务概念的进程句柄。
|
||||
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_*` 名称。
|
||||
|
||||
```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 bash execution. */
|
||||
type DshEnvironment = Readonly<Record<DshEnvironmentKey, string>>
|
||||
```
|
||||
`DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.bashEnv` 收集它们,再经由 `BashExecRequest.dshEnv` 传递;进程管理器在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
|
||||
## 请求与规格:`resolve()` 拆分
|
||||
|
||||
@@ -56,17 +46,18 @@ interface BashExecRequest {
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries for the command, merged after the credential
|
||||
* scrub. `DSH_*` is reserved for {@link dshEnv} and implementations reject it
|
||||
* here. Set by in-process plugins (the hooks bridges set
|
||||
* `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the model-facing bash tool
|
||||
* does not expose it as a parameter.
|
||||
* scrub. Managed facts belong in {@link dshEnv}, which merges after this
|
||||
* map, so an entry here can never displace one. Set by in-process plugins
|
||||
* (the hooks bridges set `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the
|
||||
* model-facing bash tool does not expose it as a parameter.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/**
|
||||
* Harness-owned `DSH_*` variables for this execution. Executors 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 managed channel.
|
||||
* Harness-owned `DSH_*` variables for this execution (typed to managed
|
||||
* keys). Executors discard ambient `DSH_*` entries before merging this
|
||||
* snapshot last, so an unavailable current fact cannot inherit a stale
|
||||
* value from the harness process and a caller {@link env} entry cannot
|
||||
* displace a managed one.
|
||||
*/
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Fully resolved per-call sandbox policy; sandboxing executors default it. */
|
||||
@@ -95,12 +86,12 @@ interface BashExecSpec {
|
||||
stdin?: string | undefined
|
||||
/**
|
||||
* Ordinary environment entries carried through from
|
||||
* {@link BashExecRequest.env}. `DSH_*` remains reserved for {@link dshEnv}.
|
||||
* {@link BashExecRequest.env}; {@link dshEnv} still merges after them.
|
||||
* OPTIONAL on the spec for the same reason as `stdin`: absent means no
|
||||
* ordinary extra environment.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
/** Managed `DSH_*` snapshot; implementations reject ordinary names. */
|
||||
/** Managed `DSH_*` snapshot (typed to managed keys); merges after {@link env}. */
|
||||
dshEnv?: DshEnvironment | undefined
|
||||
/** Resolved sandbox policy; ignored by executors that do not confine. */
|
||||
sandboxPolicy: SandboxExecutionPolicy | undefined
|
||||
@@ -145,19 +136,7 @@ interface BashRunResult {
|
||||
}
|
||||
```
|
||||
|
||||
每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息。截断时,`text` 是**尾部**,完整流溢出到一个私有文件:
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。该形状归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
|
||||
## 文件沙箱:`BashSandboxInfo`
|
||||
|
||||
@@ -192,8 +171,9 @@ interface BashSandboxInfo {
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A background process handle returned by {@link BashExecutor.start}. It is the
|
||||
* only access path; buffered output remains readable after exit. Executor
|
||||
* disposal kills running processes and awaits {@link done}.
|
||||
* only access path; buffered output remains readable after exit. Composition
|
||||
* teardown (the subprocess service's disposal) kills running processes and
|
||||
* awaits {@link done}; an executor-only reload leaves them running.
|
||||
*/
|
||||
interface BashProcess {
|
||||
/** Process lifecycle state (settled exactly once). */
|
||||
@@ -238,4 +218,4 @@ interface BashProcessRead {
|
||||
|
||||
## 服务
|
||||
|
||||
`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有进程组、超时/中止处理、有界收集器、spill 文件、凭据清除以及 dispose(资源释放)后完全停稳。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。
|
||||
`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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: 86aea325f5401d728a9b4aa147d78db9163a32c1
|
||||
core.zh.md: d09fb48419c14959eefb5a1df1c593b36c188cf5
|
||||
# pnpm run verify-translation-pairing --write docs/core-data-structures/core.md
|
||||
core.md: 1fb3288a6d01860220191f0ee1f914804dd2b33e
|
||||
core.zh.md: 74ddc5f935c8138a7fdda601650f08702dae34d3
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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` 分类体系 |
|
||||
|
||||
6
docs/core-data-structures/subprocess.i18n.yaml
Normal file
6
docs/core-data-structures/subprocess.i18n.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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: 922e7ad0ee8b5c0dbcd0a6a4553c9d2a580f3ee2
|
||||
subprocess.zh.md: 5befdcdfc9b0e1d2a9adc825b177c90e53269def
|
||||
239
docs/core-data-structures/subprocess.md
Normal file
239
docs/core-data-structures/subprocess.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Subprocess
|
||||
|
||||
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 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 the caller's explicit `env` merges, so a current fact arrives only as a deliberate entry, 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. */
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
## Node-shaped stdio dispositions
|
||||
|
||||
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
|
||||
/**
|
||||
* 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
|
||||
/** 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 — 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
|
||||
/**
|
||||
* Explicit environment entries merged onto the implementation's scrubbed
|
||||
* parent base (see `scrubbedParentEnv`), with no namespace validation:
|
||||
* every entry is a deliberate caller opt-in, so a forwarded
|
||||
* credential-shaped entry or a current `DSH_*` fact survives precisely
|
||||
* because this layer merges after the scrub that drops its ambient
|
||||
* namesake.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## Handles: streams, readers, and tree-scoped termination
|
||||
|
||||
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: `terminate()` — the only termination verb — escalates SIGTERM→grace→SIGKILL, and `waitForExit()` observes the whole tree — enough for a consumer to build its own teardown ladder (the ACP backend's stdin-EOF-first `disposeAcpChild` is the template).
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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 (tree root); -1 when the spawn itself failed. */
|
||||
readonly pid: number
|
||||
/** 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 the SIGTERM → `graceMs` → SIGKILL escalation on the process tree
|
||||
* (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.
|
||||
*/
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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; `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 {
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
```
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 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
|
||||
}
|
||||
```
|
||||
|
||||
## 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 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.
|
||||
239
docs/core-data-structures/subprocess.zh.md
Normal file
239
docs/core-data-structures/subprocess.zh.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# 进程管理器
|
||||
|
||||
[English](subprocess.md) | 中文
|
||||
|
||||
进程管理器 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 所有的子进程事实;实现会在合并调用方显式 `env` 之前丢弃环境中已有的 `DSH_*` 名称,因此当前事实只会以有意提供的条目形式到达,每条被收集的流都通过 `CollectedOutput` 报告自身的截断与 spill 恢复状态。
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
## Node 形状的 stdio 处置方式(disposition)
|
||||
|
||||
每条流的处置方式都显式给出,由各消费方自行选择:原始管道用于协议分帧(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
|
||||
}
|
||||
```
|
||||
|
||||
## 完全显式的 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
|
||||
/** 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 — 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
|
||||
/**
|
||||
* Explicit environment entries merged onto the implementation's scrubbed
|
||||
* parent base (see `scrubbedParentEnv`), with no namespace validation:
|
||||
* every entry is a deliberate caller opt-in, so a forwarded
|
||||
* credential-shaped entry or a current `DSH_*` fact survives precisely
|
||||
* because this layer merges after the scrub that drops its ambient
|
||||
* namesake.
|
||||
*/
|
||||
env?: Record<string, string> | undefined
|
||||
}
|
||||
```
|
||||
|
||||
## 句柄:流、读取器与以进程树为范围的终止
|
||||
|
||||
spawn 会立即返回一个实时句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`terminate()`(唯一的终止动词)执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树——这足以让消费方构建自己的拆卸阶梯(ACP 后端以 stdin EOF 打头的 `disposeAcpChild` 即是模板)。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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 (tree root); -1 when the spawn itself failed. */
|
||||
readonly pid: number
|
||||
/** 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 the SIGTERM → `graceMs` → SIGKILL escalation on the process tree
|
||||
* (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.
|
||||
*/
|
||||
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>
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* 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; `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 {
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
```
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 结果只承载退出事实
|
||||
|
||||
`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
|
||||
}
|
||||
```
|
||||
|
||||
## 服务行为
|
||||
|
||||
抽象的 [`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)。
|
||||
Reference in New Issue
Block a user