Merge remote-tracking branch 'origin/worktree-process-service-seam' into subprocess-simpl/c-one-termination-verb

# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-26-subprocess-consumer-migration.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-26-subprocess-consumer-migration.md
#	.agents/notes/implemented/architecture/2026-07-26-subprocess-consumer-migration.zh.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/subprocess.i18n.yaml
#	docs/core-data-structures/subprocess.md
#	docs/core-data-structures/subprocess.zh.md
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/subprocess/subprocess-local/README.i18n.yaml
#	packages/subprocess/subprocess-local/README.md
#	packages/subprocess/subprocess-local/README.zh.md
#	packages/subprocess/subprocess/README.i18n.yaml
#	packages/subprocess/subprocess/README.md
#	packages/subprocess/subprocess/README.zh.md
#	packages/subprocess/subprocess/src/index.ts
This commit is contained in:
Tianyi Cui
2026-07-27 11:22:21 +08:00
32 changed files with 224 additions and 296 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ The subprocess seam (`ctx.subprocess`). The abstract `SubprocessService` exposes
- `spawn(spec)` returns immediately with a live handle; `done` resolves at process close with exit facts (`SubprocessOutcome` carries no output and no cause classification) and rejects only for spawn-level failures.
- The spec is fully explicit — argv, cwd, per-stream stdio dispositions, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden subprocess-service default (the `dsh-bash` request/spec split is the owning template). `argv` is never shell-interpreted; a consumer that wants a shell passes `['bash', '-c', command]` itself.
- Stdio is Node-shaped per stream: `'pipe'` hands the caller the raw stream for its own protocol framing (LSP JSON-RPC, ACP ndjson), `'inherit'` passes the parent descriptor through for diagnostics, and collect mode (`{ maxBytes, spill? }`) buffers a bounded tail with an optional full-stream spill file. Collect readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; a read whose offset slid out of the in-memory tail is `lossy` and points at the spill file when one exists. Collected output stays readable after settlement.
- Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows `taskkill /T`): `terminate()` — the only termination verb — escalates SIGTERM→grace→SIGKILL (idempotent, driven by the spec's abort signal too, a no-op once the tree is gone), `waitForExit()` observes the whole tree, and `dispose(graces)` runs the cooperative stdin-EOF→SIGTERM→SIGKILL ladder out-of-process children need — the manager reacts but never classifies why (callers own deadlines and cause classification).
- Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows `taskkill /T`): `terminate()` — the only termination verb — escalates SIGTERM→grace→SIGKILL (idempotent, driven by the spec's abort signal too, a no-op once the tree is gone), and `waitForExit(signal?)` observes whole-tree liveness so a consumer-owned teardown ladder holds each tier on real quiescence — the manager reacts but never classifies why (callers own deadlines, teardown ladders, and cause classification).
- `scrubbedParentEnv()` / `SENSITIVE_ENV_PATTERN` are the one shared scrub definition: ambient credential-shaped and `DSH_*` names are dropped, explicit `env` merges after the scrub (a deliberately forwarded key survives), and `dshEnv` carries current harness facts on its own validated channel; `splitEnvChannels()` partitions a consumer config's single mixed env map onto those two channels (lsp-local servers and the ACP backend expose one map, and a configured `DSH_*` fact must ride the managed channel the ordinary one rejects). Spawners that cannot route through the service (node-pty backends, SDK-managed transports) import the scrub.
- Disposal of the service terminates all still-running managed processes and awaits their exit.
@@ -26,4 +26,4 @@ No direct invalidation; the named consumers own any request-prefix changes.
## Known Limitations and Deferred Work
- **node-pty and SDK-managed spawns share only the scrub** — the PTY backend's terminal fork and the MCP SDK's own stdio transport cannot route their spawns through this seam (the library owns the fork/spawn call); they import `scrubbedParentEnv` so the environment policy stays single-sourced.
- **The dispose ladder assumes stdin-EOF cooperation** — a child that quiesces on a different signal (SIGHUP conventions, control sockets) needs its own tier-1 before the generic ladder fits.
- **Teardown ladders are consumer-owned** — the seam ships signalling verbs and the tree-liveness wait, not a canned quiesce sequence; each out-of-process consumer encodes its child's cooperation shape itself (the ACP backend's stdin-EOF-first ladder is the in-repo template).