refactor(subprocess): rename the process seam to subprocess and address review
Review feedback (tianyicui): 'process' is a poor service name. The family is now packages/subprocess/ — @deepseek-ai/dsh-subprocess (ctx.subprocess, abstract SubprocessService, Subprocess* vocabulary) and @deepseek-ai/dsh-subprocess-local (LocalSubprocessService) — renamed throughout code, compositions, docs (en+zh, pairs re-recorded), catalogs, and gates. 'subprocess' is the precise term for managed OS children (the Python-stdlib sense), avoids colliding with Node's global process object, and reads as one system beside dsh-subagent-subprocess. ds-review-bot findings addressed: - kill() on a settled handle is now a no-op (no signal to a possibly-reused pgid, no referenced grace timer delaying exit); pinned by a spy test. - The moved DshEnvironmentKey/DshEnvironment/CollectedOutput types get drift-checked type-equiv blocks on the new subprocess.md page, restoring their manifest registration. - subprocess.md is registered in the core.md sub-page index (en+zh).
This commit is contained in:
53
packages/subprocess/subprocess-local/src/index.ts
Normal file
53
packages/subprocess/subprocess-local/src/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 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).
|
||||
* @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 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.
|
||||
*/
|
||||
export class LocalSubprocessService extends SubprocessService {
|
||||
/** Live handles retained only so disposal can kill and join them. */
|
||||
private live = new Set<SubprocessHandle>()
|
||||
/** 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 subprocess teardown')
|
||||
}
|
||||
|
||||
spawn(spec: SubprocessSpawnSpec): SubprocessHandle {
|
||||
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 LocalSubprocessService
|
||||
Reference in New Issue
Block a user