Files
deepseek-harness/packages/process/process-local/src/index.ts
T
Tianyi Cui 0d6bfd8856 refactor(process): split the process manager out of the bash executor
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).
2026-07-26 06:59:01 +08:00

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