feat(subprocess): reshape the seam Node-ward for multi-consumer use
Review direction (tianyicui, PR #660): make the interface closer to Node's API so the other process-running places can adopt it. The spec gains per-stream stdio dispositions — 'pipe' (raw Readable/Writable for protocol streams), 'inherit' (diagnostics to the parent), and collect mode ({maxBytes, spill?} — the old bounded tail-keep shape, now with spill optional for diagnostic tails). SubprocessOutcome carries exit facts only; collected output stays readable through handle.collected after settlement (spill fds are sealed at the settle boundary). The handle grows Node-style kill(signal) (single signal, tree-scoped, no-op after settlement), terminate() (the SIGTERM→grace→SIGKILL escalation, also driven by the spec signal), waitForExit() (tree liveness, not just the direct child), and dispose() (the cooperative stdin-EOF→SIGTERM→SIGKILL ladder from subagent-subprocess, graces caller-supplied). Tree semantics are platform-correct: POSIX detached groups with direct-child fallback; Windows taskkill /T with an injectable runner. scrubbedParentEnv/SENSITIVE_ENV_PATTERN move to the seam as the one shared scrub definition. bash-local maps its config onto collect modes and batch stdin and reads results through the collected readers; its kill() maps to terminate() so task_kill keeps escalation semantics.
This commit is contained in:
@@ -1,36 +1,38 @@
|
||||
/**
|
||||
* Local-subprocess implementation of the subprocess seam. Each spawn is
|
||||
* a detached process group with bounded, spill-backed output; disposal kills
|
||||
* and joins live groups. It has no config: every limit arrives on the spec,
|
||||
* so the deployment-varying choices stay with the calling seam's config (the
|
||||
* bash executor's, today).
|
||||
* Local implementation of the subprocess seam. Each spawn is a detached
|
||||
* process tree with the spec's per-stream stdio dispositions; disposal
|
||||
* terminates and joins live trees. It has no config: every disposition and
|
||||
* limit arrives on the spec, so the deployment-varying choices stay with the
|
||||
* calling seam's config (the bash executor's, the LSP host's, …).
|
||||
* @module @deepseek-ai/dsh-subprocess-local
|
||||
*/
|
||||
|
||||
import { Context } from 'cordis'
|
||||
import { SubprocessService } from '@deepseek-ai/dsh-subprocess'
|
||||
import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
|
||||
import { spawnProcess } from './spawn.ts'
|
||||
import { spawnSubprocess } from './spawn.ts'
|
||||
import type { SpawnInternals } from './spawn.ts'
|
||||
|
||||
/**
|
||||
* Local subprocess service: detached process groups, tail-keep truncation with
|
||||
* bounded spill files, credential-scrubbed environment, and group
|
||||
* SIGTERM→grace→SIGKILL escalation.
|
||||
* Local subprocess service: detached process trees, Node-shaped stdio
|
||||
* dispositions (raw pipes, inherit, bounded tail-keep collection with spill
|
||||
* files), credential-scrubbed environment, tree-scoped signalling with
|
||||
* SIGTERM→grace→SIGKILL escalation, and the cooperative dispose ladder.
|
||||
*/
|
||||
export class LocalSubprocessService extends SubprocessService {
|
||||
/** Live handles retained only so disposal can kill and join them. */
|
||||
/** Live handles retained only so disposal can terminate and join them. */
|
||||
private live = new Set<SubprocessHandle>()
|
||||
/** Test seam: spill knobs forwarded to spawnProcess. */
|
||||
/** Test seam: spill and platform knobs forwarded to spawnSubprocess. */
|
||||
internals: SpawnInternals = {}
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx)
|
||||
ctx.effect(() => async () => {
|
||||
// Await closure so even a TERM-trapping child cannot outlive the fiber.
|
||||
// Terminate (escalating), then await closure so even a TERM-trapping
|
||||
// child cannot outlive the fiber.
|
||||
const pending: Promise<unknown>[] = []
|
||||
for (const handle of this.live) {
|
||||
handle.kill()
|
||||
handle.terminate()
|
||||
// Spawn-failure rejections already settled and left the live set.
|
||||
pending.push(handle.done.catch(() => {}))
|
||||
}
|
||||
@@ -40,7 +42,7 @@ export class LocalSubprocessService extends SubprocessService {
|
||||
}
|
||||
|
||||
spawn(spec: SubprocessSpawnSpec): SubprocessHandle {
|
||||
const handle = spawnProcess(spec, this.internals)
|
||||
const handle = spawnSubprocess(spec, this.internals)
|
||||
this.live.add(handle)
|
||||
handle.done.then(
|
||||
() => { this.live.delete(handle) },
|
||||
|
||||
Reference in New Issue
Block a user