refactor(subprocess): the dispose ladder moves to its one consumer

SubprocessHandle loses dispose(graces) and SubprocessDisposeGraces: the
stdin-EOF→SIGTERM→SIGKILL sequence is teardown POLICY encoding one
consumer's cooperation shape, not process vocabulary — the seam keeps
kill/terminate/waitForExit, and waitForExit(signal?) is the quiescence
probe a consumer ladder needs. dsh-subagent-acp owns disposeAcpChild()
over those public verbs (tier tests move into its suite; a never-exits
stub pins the fail-loud path); dsh-subprocess-local sheds the ladder,
its deadline import, and the dsh-timeout dependency. Every future
backend now owes four verbs and no teardown policy. New bilingual
ladder-ownership Agent Note records the decision; catalogs regenerated.
This commit is contained in:
Tianyi Cui
2026-07-27 03:52:41 +08:00
parent cb1864795e
commit 776d666246
32 changed files with 223 additions and 294 deletions
+7 -8
View File
@@ -2,10 +2,9 @@
* The subprocess seam (`ctx.subprocess`): spawn fully-specified commands into
* managed process trees with Node-shaped stdio dispositions — raw pipes for
* protocol streams, inherit for diagnostics, bounded spill-backed collection
* for batch output — plus tree-scoped signalling and a cooperative dispose
* ladder. Command defaulting, shell semantics, deadlines, framing, and
* presentation belong to consumers; the bash executor seam is the owning
* template. The local implementation lives in
* for batch output — plus tree-scoped signalling. Command defaulting, shell
* semantics, deadlines, teardown ladders, framing, and presentation belong to
* consumers; the bash executor seam is the owning template. The local implementation lives in
* `@deepseek-ai/dsh-subprocess-local`.
* @module @deepseek-ai/dsh-subprocess
*/
@@ -22,7 +21,6 @@ export type {
DshEnvironmentKey,
SubprocessCollect,
SubprocessCollectedOutputs,
SubprocessDisposeGraces,
SubprocessHandle,
SubprocessOutcome,
SubprocessOutputMode,
@@ -102,10 +100,11 @@ declare module 'cordis' {
* 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.
* - {@link SubprocessHandle.kill} signals without escalation,
* - {@link SubprocessHandle.kill} signals without escalation, and
* {@link SubprocessHandle.terminate} (and the spec's abort signal) escalates
* SIGTERM→grace→SIGKILL, and {@link SubprocessHandle.dispose} runs the
* cooperative EOF-first ladder — all tree-scoped on every platform.
* SIGTERM→grace→SIGKILL — both tree-scoped on every platform.
* {@link SubprocessHandle.waitForExit} observes whole-tree liveness, so a
* consumer-owned teardown ladder can hold each tier on real quiescence.
* - Disposal of the service terminates all still-running managed processes
* and awaits their exit.
*/
@@ -160,30 +160,6 @@ export interface SubprocessCollectedOutputs {
readonly stderr?: SubprocessOutputReader
}
/**
* 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.
*/
export 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
}
/**
* A live child process rooted in its own process tree. Collected output
* remains readable after exit; piped streams belong to the caller.
@@ -226,13 +202,4 @@ export interface SubprocessHandle {
* @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>
}