English | 中文
Local Service Provider for the @deepseek-ai/dsh-subprocess seam. LocalSubprocessRuntime resolves local executables, gives ordinary Linux and Windows commands plus Linux terminal sessions an OS-owned managed range when the host supports it, and implements terminal processes through node-pty plus platform process inspection. It has no config: every disposition, limit, terminal dimension, grace, and directory arrives from the calling capability seams (dsh-bash-local, dsh-lsp-stdio, and dsh-terminal-bash).
ActiveProcesses reaches zero; the parent never opens either native object. Linux scopes and POSIX process-group fallbacks receive TERM and then KILL after graceMs; Windows Job and taskkill owners force-terminate on the first request. waitForExit() succeeds only after the selected owner proves the range empty and rejects when that proof is unavailable. After the direct result arrives, .done waits up to graceMs for every non-inherited output stream to close; at that bound, only collected streams are force-closed while raw pipes remain caller-owned.taskkill /T path. The provider warns once before the first affected command. It never retries through fallback after a native runner may have started the user command.'pipe' hands the raw stream to the caller untouched (protocol framing stays consumer-owned); 'inherit' passes the parent descriptor through; collect mode keeps the in-memory TAIL beyond its cap (errors and results cluster at the end — pi/OpenCode rationale) while the FULL stream is appended to a private temp file when a spill cap is configured — omitting spill keeps only the tail, the diagnostic shape. A stream larger than the spill cap discards its now-incomplete spill and returns only the marked truncated tail; spill fds are sealed at settlement, and a failed final close withholds the path rather than advertising an incomplete file. Spill files are 0600 with random names under a lazily-created 0700 per-process directory.process.env minus credential-shaped vars (*KEY*/*PASSWORD*/*SECRET*/*TOKEN*) and all ambient DSH_* names; the spec's explicit env merges after that scrub with no namespace validation, so a deliberately supplied credential or current DSH_* fact wins while stale nested-harness identity cannot leak in ambiently. Supplied stdin is written and closed; otherwise fd 0 is /dev/null. See the stdin/env Agent Note and managed environment Agent Note.resolveExecutable checks absolute files or searches the scrubbed effective PATH with platform-aware executable extensions; relative paths containing separators are rejected at the seam, and relative PATH entries resolve from the host process cwd.spawnTerminal allocates node-pty, bridges UTF-8 terminal text, inspects and signals the current foreground process group, and exposes one awaited termination operation. Supported Linux hosts wrap the original terminal argv directly in the same user-systemd scope model as ordinary commands, preserving the node-pty PID, session leader, controlling terminal, and foreground-input inspection while the scope owns reparented or setsid descendants. Linux reports an exact input wait only when the waiting thread's own fd 0 identifies the shell's controlling terminal, including the /dev/tty alias, so a pipeline reader blocked on pipe:[…] cannot publish terminal readiness. The syscall probe admits supported runtime architectures and matches every supported kernel ABI so user-mode emulation cannot hide the wait. When Linux denies /proc/<pid>/task/<tid>/syscall, the inspector reports no exact wait and leaves the higher PTY backend to its configured idle inference; process sleep state never substitutes for syscall evidence. On fallback hosts, each foreground inspection retains exact identities from the rooted tree, Linux also enumerates the POSIX session after its leader exits, and teardown sweeps observable descendants before and after terminating the top-level shell. A previously observed macOS descendant and any same-session fallback Linux member therefore remain fenced after reparenting, while pid/start identity prevents cleanup from following PID reuse. On Windows the koffi-backed inspector enumerates the process table through Toolhelp32, combines GetProcessTimes start identities with zero-time process-handle waits for liveness, reports the shell pid as the pseudo foreground group (Windows has no POSIX groups), and teardown verifies the shell's termination because externally taskkilled shells may never fire node-pty's exit notification. The higher PTY backend owns prompt readiness, buffers, and model-facing operations.exit listener synchronously signals every ordinary managed range and terminal session still in the live sets. Linux native owners issue the scope KILL request; the Windows runner treats parent IPC disconnect as Job termination; fallback owners retain their PGID, taskkill, and captured-identity behavior. These local-only operations create no promise or timer, preserve the host exit code and diagnostic, contain each target failure, and do not claim quiescence. Normal disposal keeps the awaited managed-range or graceful fallback path above. See the host-exit cleanup decision.Indirectly, through Consumers (the bash executor family behind dsh-tool-bash), which own all model-facing rendering of process output and lifecycle.
No direct invalidation; the named consumers own any request-prefix changes.
systemd-run --expand-environment=no; older systemd versions use the warned PGID fallback. macOS always uses that fallback because no supported public persistent owner exists.pid starts as undefined and updates from asynchronously polled runner events, while .done carries target startup failure or direct outcome. There is no target-publication timeout; a runner that remains alive without a terminal event leaves pid undefined and .done pending until it exits or the range is terminated. Each supported native ordinary command keeps one runner process alive until the OS-owned range is empty, and Windows additionally creates private per-spawn named-pipe endpoints. Linux terminal launch passes the scoped argv directly to node-pty and adds no runner. Runner events are polled asynchronously every 100 ms and Linux scope state every 200 ms.\x03 Ctrl-C input write that conhost turns into a console-wide CTRL_C event; SIGTSTP and SIGHUP are rejected as unavailable; a taskkill without /F does not terminate console processes, so the teardown TERM tier is a grace wait before the /F escalation. Windows readiness has no exact stdin-wait tier: the prompt-marker fast path compares the shell pid as the pseudo foreground group, and silence/timing tiers cover the rest.process.exit(), default uncaught exceptions, and default unhandled rejections emit Node's synchronous exit event. The default OS disposition for an unhandled SIGTERM, SIGINT, or SIGHUP bypasses that event; an application covers those signals only by installing a handler that performs normal disposal or calls process.exit(). SIGKILL, fatal OOM, process.abort(), native crashes, power loss, and any failure that cannot run JavaScript require an external supervisor, container init, or equivalent OS owner.*KEY*/*PASSWORD*/*SECRET*/*TOKEN* only; differently-named secrets (e.g. *PASSPHRASE*) pass through, and a whitelist for over-scrubbed vars is noted future work.Common process handling lives in src/spawn.ts; Linux scopes, Windows Jobs, and the private runner live in their platform modules; src/index.ts owns selection and service wiring.