New process/ capability family: @deepseek-ai/dsh-process owns ctx.processes — abstract ProcessManager.spawn(spec) over a fully-explicit ProcessSpawnSpec — plus the shared DSH_* managed-environment and CollectedOutput vocabulary; @deepseek-ai/dsh-process-local carries the former bash-local run.ts plumbing (detached groups, tail-keep spill-backed output, credential scrub, kill escalation, kill-and-join disposal) with no config of its own. dsh-bash-local becomes a consumer: it keeps command defaulting, the fused deadline timedOut/aborted classification, the model-friendly terminal env (now merged through the ordinary env channel), and the [stderr]-marked background read merge, and spawns through ctx.processes. Background-process lifetime moves to the manager, so an executor reload no longer kills live background work; a background spawn failure is injected once into the read path instead of being buffered as fake stderr. dsh-bash re-exports the moved vocabulary so bash consumers keep one import root; dsh-bash-sandbox only redeclares the inherited inject. Every composition loading a bash executor now loads dsh-process-local (CLI, examples, python bundled runtime, create-sdk bash feature, inline test configs).
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
/**
|
|
* Local-subprocess implementation of the process-manager 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).
|
|
* @module @deepseek-ai/dsh-process-local
|
|
*/
|
|
|
|
import { Context } from 'cordis'
|
|
import { ProcessManager } from '@deepseek-ai/dsh-process'
|
|
import type { ProcessHandle, ProcessSpawnSpec } from '@deepseek-ai/dsh-process'
|
|
import { spawnProcess } from './spawn.ts'
|
|
import type { SpawnInternals } from './spawn.ts'
|
|
|
|
/**
|
|
* Local process manager: detached process groups, tail-keep truncation with
|
|
* bounded spill files, credential-scrubbed environment, and group
|
|
* SIGTERM→grace→SIGKILL escalation.
|
|
*/
|
|
export class LocalProcessManager extends ProcessManager {
|
|
/** Live handles retained only so disposal can kill and join them. */
|
|
private live = new Set<ProcessHandle>()
|
|
/** Test seam: spill knobs forwarded to spawnProcess. */
|
|
internals: SpawnInternals = {}
|
|
|
|
constructor(ctx: Context) {
|
|
super(ctx)
|
|
ctx.effect(() => async () => {
|
|
// Await closure so even a TERM-trapping child cannot outlive the fiber.
|
|
const pending: Promise<unknown>[] = []
|
|
for (const handle of this.live) {
|
|
handle.kill()
|
|
// Spawn-failure rejections already settled and left the live set.
|
|
pending.push(handle.done.catch(() => {}))
|
|
}
|
|
this.live.clear()
|
|
await Promise.all(pending)
|
|
}, 'local process-manager teardown')
|
|
}
|
|
|
|
spawn(spec: ProcessSpawnSpec): ProcessHandle {
|
|
const handle = spawnProcess(spec, this.internals)
|
|
this.live.add(handle)
|
|
handle.done.then(
|
|
() => { this.live.delete(handle) },
|
|
() => { this.live.delete(handle) },
|
|
)
|
|
return handle
|
|
}
|
|
}
|
|
|
|
export default LocalProcessManager
|