| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- /**
- * Service Definition for the subprocess capability seam (`ctx.subprocess`): execution-world executable lookup,
- * fully specified managed process trees with raw or
- * collected stdio, and one terminal-process primitive. Command defaulting,
- * shell semantics, deadlines, protocol framing, terminal readiness, and
- * presentation belong to consumers. The local implementation lives in
- * `@deepseek-ai/dsh-subprocess-local`.
- * @module @deepseek-ai/dsh-subprocess
- */
- import { Context, Service } from '@deepseek-ai/cordis'
- import { DSH_ENV_PREFIX } from './types.ts'
- import type { SubprocessHandle, SubprocessSpawnSpec } from './types.ts'
- import type { SubprocessTerminalHandle, SubprocessTerminalSpawnSpec } from './types.ts'
- export { DSH_ENV_PREFIX } from './types.ts'
- export type {
- CollectedOutput,
- DshEnvironment,
- DshEnvironmentKey,
- SubprocessCollect,
- SubprocessCollectedOutputs,
- SubprocessHandle,
- SubprocessOutcome,
- SubprocessOutputMode,
- SubprocessOutputRead,
- SubprocessOutputReader,
- SubprocessSpawnSpec,
- SubprocessStdinMode,
- SubprocessStdio,
- SubprocessTerminalForeground,
- SubprocessTerminalHandle,
- SubprocessTerminalSignal,
- SubprocessTerminalSpawnSpec,
- } from './types.ts'
- /**
- * Credential-shaped environment names are NOT forwarded to children (the
- * harness's own `DEEPSEEK_API_KEY`/secrets must not leak into a spawned
- * process implicitly). One heuristic for every in-repo spawner; a
- * deliberately supplied entry survives because explicit env layers merge
- * after the scrub.
- */
- export const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i
- /**
- * The ambient parent environment minus credential-shaped names and minus all
- * `DSH_*` names — the canonical base every harness child starts from. `PATH`,
- * `HOME`, locale, and proxy variables survive, so child CLIs run normally;
- * harness identity never leaks implicitly (a deliberately forwarded
- * credential or current `DSH_*` fact goes through the spec's explicit `env`,
- * which merges after this scrub). Both scrubs match case-insensitively:
- * Windows environment names are case-insensitive, so a parent `dsh_*` entry
- * would otherwise survive and read back as `$env:DSH_*` in the child;
- * deliberate lowercase `dsh_*` names on POSIX are implausible. Exported as a plain function so spawners
- * that cannot route through the service (node-pty backends, SDK-managed
- * transports) share the one scrub definition.
- * @returns a fresh environment object safe to hand to a child spawn.
- */
- export function scrubbedParentEnv(): Record<string, string> {
- const env: Record<string, string> = {}
- for (const [key, value] of Object.entries(process.env)) {
- if (value !== undefined && !SENSITIVE_ENV_PATTERN.test(key) && !key.toUpperCase().startsWith(DSH_ENV_PREFIX)) env[key] = value
- }
- return env
- }
- declare module '@deepseek-ai/cordis' {
- interface Context {
- subprocess: SubprocessRuntime
- }
- }
- /**
- * Abstract subprocess service. Subclass, implement {@link spawn}, and load the
- * subclass as a plugin — it registers as `ctx.subprocess` (one implementation
- * per context; loading a second throws, which is cordis' standard
- * duplicate-service behavior).
- *
- * Implementations must honor these semantics:
- * - Executable paths belong to one execution world shared with the mounted
- * filesystem provider.
- * - {@link spawn} returns immediately with a live handle; `done` resolves at
- * process close with exit facts and rejects only for spawn-level failures.
- * - Collect-mode readers are offset-based and non-consuming, so independent
- * readers never consume one another's output; lossy reads report truncation
- * and the spill file holding the complete stream when one exists. Piped
- * streams are handed to the caller raw and never buffered here.
- * - {@link SubprocessHandle.terminate} (and the spec's abort signal) escalates
- * SIGTERM→grace→SIGKILL — the only termination verb — tree-scoped on every
- * platform. {@link SubprocessHandle.waitForExit} observes whole-tree
- * liveness, so a consumer-owned teardown ladder can hold each tier on real
- * quiescence.
- * - Disposal of the service terminates all still-running managed processes
- * and awaits their exit.
- * - {@link spawnTerminal} owns terminal allocation, text transport,
- * foreground groups, signalling, and whole-session quiescence behind one
- * awaited termination method; readiness and persistent-shell policy stay
- * in the PTY consumer. Its output stream ends after queued terminal output
- * when the top-level process exits.
- */
- export abstract class SubprocessRuntime extends Service {
- constructor(ctx: Context) {
- super(ctx, 'subprocess')
- }
- /**
- * Resolve one configured executable in this provider's execution world.
- * Absolute paths are verified; bare names use the provider's scrubbed PATH
- * plus explicit environment overrides. Relative paths containing separators
- * are rejected: the resolution base is undefined, so providers fail loud
- * instead of guessing.
- * @param command - absolute executable path or bare PATH name.
- * @param env - explicit environment entries used for lookup.
- * @param signal - aborts remote or local lookup.
- * @returns a canonical executable path.
- */
- abstract resolveExecutable(
- command: string,
- env?: Readonly<Record<string, string>>,
- signal?: AbortSignal,
- ): Promise<string>
- /**
- * Start one managed child process from a fully-specified spec; this seam
- * applies no defaults.
- * @param spec - argv, directory, stdio dispositions, grace, cancellation, and environment.
- * @returns the live process handle (streams/readers, signalling, outcome promise).
- */
- abstract spawn(spec: SubprocessSpawnSpec): SubprocessHandle
- /**
- * Allocate a real terminal and start one owned process session. This is the
- * only non-pipe process primitive: implementations own terminal byte I/O,
- * foreground groups, signals, and complete session-tree cleanup.
- * @param spec - fully specified argv, cwd, environment, dimensions, grace, and allocation cancellation.
- * @returns the live terminal handle after allocation succeeds.
- */
- abstract spawnTerminal(spec: SubprocessTerminalSpawnSpec): Promise<SubprocessTerminalHandle>
- }
- export default SubprocessRuntime
|