Files
deepseek-harness/packages/subprocess/subprocess
Huanqi Cao b2838fb2be fix(pwsh): close the remaining review threads
- scrubbedParentEnv folds case before the DSH_ prefix check (Windows env
  names are case-insensitive; a parent dsh_* entry read back as \*
  in the child) and the service spec pins the lowercase probe.
- The acp.snapshot.ts pwsh probe follows resolvePwshPath() like the package
  suites, so a Windows host with only an install-location pwsh still runs
  the scenario.
- pwsh-tool-turn is re-recorded around [Console]::Out.Write('PWSH_OK'):
  the fixture carries no platform newline, so one recording replays on
  Windows and POSIX alike (record + refresh; replay-verified keyless).
- The pwsh-local Known Limitations bullet drops the self-defeating no-op
  advice: & { } is scoped to param(...), using/#requires scripts run from a
  file (both languages, pairing re-recorded).
- The capability-seams graph moves ctx.bashEnv ownership to bash-env and
  lists pwsh-local/tool-pwsh on the ctx.bash seam (source updated,
  docs regenerated).
- The tool-bash presenter fixture retires the stale 'command aborted'
  literal for the shipped 'tool call aborted' message.
2026-08-02 21:46:53 +08:00
..

@deepseek-ai/dsh-subprocess

English | 中文

The subprocess seam (ctx.subprocess). The abstract SubprocessService exposes one method — spawn(spec): SubprocessHandle — plus the vocabulary shared by every consumer: the fully-explicit SubprocessSpawnSpec, SubprocessHandle with its non-consuming offset-based output readers, SubprocessOutcome, CollectedOutput, and the managed DSH_* environment namespace (DSH_ENV_PREFIX, DshEnvironment). The local implementation lives in dsh-subprocess-local.

Contract

  • spawn(spec) returns immediately with a live handle; done resolves at process close with exit facts (SubprocessOutcome carries no output and no cause classification) and rejects only for spawn-level failures.
  • The spec is fully explicit — argv, cwd, per-stream stdio dispositions, grace — because deployment-varying defaults belong to the calling seam's config, not to a hidden subprocess-service default (the dsh-bash request/spec split is the owning template). argv is never shell-interpreted; a consumer that wants a shell passes ['bash', '-c', command] itself.
  • Stdio is Node-shaped per stream: 'pipe' hands the caller the raw stream for its own protocol framing (LSP JSON-RPC, ACP ndjson), 'inherit' passes the parent descriptor through for diagnostics, and collect mode ({ maxBytes, spill? }) buffers a bounded tail with an optional full-stream spill file. Collect readers take whole-stream byte offsets and never consume, so independent readers cannot steal one another's deltas; a read whose offset slid out of the in-memory tail is lossy and points at the spill file when one exists. Collected output stays readable after settlement.
  • Termination is tree-scoped on every platform (POSIX detached groups with direct-child fallback; Windows taskkill /T): terminate() — the only termination verb — escalates SIGTERM→grace→SIGKILL (idempotent, driven by the spec's abort signal too, a no-op once the tree is gone), and waitForExit(signal?) observes whole-tree liveness so a consumer-owned teardown ladder holds each tier on real quiescence — the manager reacts but never classifies why (callers own deadlines, teardown ladders, and cause classification).
  • scrubbedParentEnv() / SENSITIVE_ENV_PATTERN are the one shared scrub definition: ambient credential-shaped and DSH_* names are dropped, and the spec's explicit env merges after the scrub with no namespace validation — a deliberately forwarded credential or a current DSH_* fact survives precisely because it is an explicit caller opt-in, while the stale ambient namesake never reaches the child. Spawners that cannot route through the service (node-pty backends, SDK-managed transports) import the scrub.
  • Disposal of the service terminates all still-running managed processes and awaits their exit.

See the subprocess data-structure catalog and the seam Agent Note.

Model Experience

Indirectly, through consumer seams (today the bash executor family behind dsh-tool-bash), which own all model-facing rendering of process output and lifecycle.

KV Cache effect

No direct invalidation; the named consumers own any request-prefix changes.

Known Limitations and Deferred Work

  • node-pty and SDK-managed spawns share only the scrub — the PTY backend's terminal fork and the MCP SDK's own stdio transport cannot route their spawns through this seam (the library owns the fork/spawn call); they import scrubbedParentEnv so the environment policy stays single-sourced.
  • Teardown ladders are consumer-owned — the seam ships signalling verbs and the tree-liveness wait, not a canned quiesce sequence; each out-of-process consumer encodes its child's cooperation shape itself (the ACP backend's stdin-EOF-first ladder is the in-repo template).