@deepseek-ai/dsh-subprocess-local 0.1.5-rc.2 → 0.1.6-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,8 +49,18 @@ export declare function probeLinuxManager(internals?: LinuxScopeInternals): bool
49
49
  export declare function probeLinuxNative(internals?: LinuxScopeInternals): boolean;
50
50
  interface DirectRange {
51
51
  running(): boolean;
52
- signal(signal: 'SIGTERM' | 'SIGKILL'): void;
52
+ /** True for group TERM delivery, direct signal submission, or proven direct-PID absence. */
53
+ signal(signal: 'SIGTERM' | 'SIGKILL'): boolean;
54
+ /** Direct exit/error settlement, independent of output drain and managed-range completion. */
55
+ settled: Promise<unknown>;
53
56
  }
57
+ /**
58
+ * Send a direct-process signal, distinguishing an absent PID from failed delivery.
59
+ * @param pid - owned direct-process identity whose exit notification can still be pending.
60
+ * @param send - platform signal operation; true means the signal was submitted.
61
+ * @returns whether the signal was submitted or the owned PID is already absent.
62
+ */
63
+ export declare function signalLinuxDirectProcess(pid: number, send: () => boolean): boolean;
54
64
  /** Linux PTY invocation and owner for the exact one-shot scope/bootstrap. */
55
65
  export interface LinuxTerminalScopeLaunch {
56
66
  command: string;
@@ -66,7 +76,7 @@ export interface LinuxTerminalScopeLaunch {
66
76
  * @param spec - terminal target request.
67
77
  * @param targetEnv - validated complete target environment.
68
78
  * @param internals - optional runner and systemd seams used by tests.
69
- * @returns invocation facts and ownership callbacks for node-pty.
79
+ * @returns invocation and ownership callbacks; requested termination preserves the observed signal even before bootstrap consumption.
70
80
  */
71
81
  export declare function prepareLinuxTerminalScope(spec: SubprocessTerminalSpawnSpec, targetEnv: Record<string, string>, internals?: LinuxScopeInternals): LinuxTerminalScopeLaunch;
72
82
  /**
@@ -74,7 +84,7 @@ export declare function prepareLinuxTerminalScope(spec: SubprocessTerminalSpawnS
74
84
  * @param spec - ordinary target request.
75
85
  * @param targetEnv - validated complete target environment.
76
86
  * @param internals - optional runner and systemd seams used by tests.
77
- * @returns direct streams, result, and managed-scope owner.
87
+ * @returns streams, result, and scope owner; requested termination preserves the observed signal even before bootstrap consumption.
78
88
  */
79
89
  export declare function launchLinuxScope(spec: SubprocessSpawnSpec, targetEnv: Record<string, string>, internals?: LinuxScopeInternals): ManagedProcessLaunch;
80
90
  export {};
@@ -1,5 +1,5 @@
1
1
  /** Minimal managed-range ownership bound to one ordinary subprocess handle. */
2
- import type { Readable, Writable } from 'node:stream';
2
+ import type { Duplex, Readable, Writable } from 'node:stream';
3
3
  import type { SubprocessOutcome } from '@deepseek-ai/dsh-subprocess';
4
4
  /** Platform owner used by termination and whole-range settlement. */
5
5
  export interface BoundProcessOwner {
@@ -7,6 +7,11 @@ export interface BoundProcessOwner {
7
7
  signal(signal: 'SIGTERM' | 'SIGKILL', cancellationReason?: unknown): void;
8
8
  /** Wait for the same managed range to become empty; reject when it cannot be observed. */
9
9
  waitForExit(): Promise<void>;
10
+ /**
11
+ * Count tasks in the native range, including descendants outside the observable process tree.
12
+ * @returns the current count, or undefined when observation is unavailable.
13
+ */
14
+ inspectTaskCount?(): number | undefined;
10
15
  /** Synchronously force final termination during JavaScript-observable host exit. */
11
16
  terminateForHostExit(): void;
12
17
  /** Release provider-private protocol artifacts after outcome and range settlement. */
@@ -17,6 +22,7 @@ export interface ManagedProcessLaunch {
17
22
  stdin: Writable | null;
18
23
  stdout: Readable | null;
19
24
  stderr: Readable | null;
25
+ control?: Duplex | undefined;
20
26
  direct: Promise<SubprocessOutcome>;
21
27
  owner: BoundProcessOwner;
22
28
  }
@@ -0,0 +1,85 @@
1
+ import type { CollectedOutput } from '@deepseek-ai/dsh-subprocess';
2
+ /**
3
+ * Prepare fallible output storage before starting a managed native process.
4
+ * @param internals - optional caller-owned spill directory.
5
+ * @returns binding inputs whose spill directory is ready for use.
6
+ */
7
+ export declare function prepareManagedProcessBinding(internals?: {
8
+ spillDir?: string;
9
+ }): {
10
+ spillDir: string;
11
+ };
12
+ /**
13
+ * Collects one stream with a bounded in-memory tail. With a spill cap, on
14
+ * first overflow a spill file is created and every chunk (including those
15
+ * already collected) is appended there while the full stream remains within
16
+ * the cap; without one, only the in-memory tail is ever retained (the
17
+ * diagnostic-tail shape — a language server's stderr).
18
+ *
19
+ * Tail-keep rationale (pi/OpenCode): errors and final results cluster at the
20
+ * end of command output; the spill file covers the head.
21
+ */
22
+ export declare class OutputCollector {
23
+ private readonly maxBytes;
24
+ private readonly maxSpillBytes;
25
+ private readonly label;
26
+ private readonly spillDir;
27
+ private chunks;
28
+ private bytes;
29
+ private dropped;
30
+ private spillFd;
31
+ private spillFile;
32
+ private spillDisabled;
33
+ /** Total bytes ever pushed (not just retained). */
34
+ private total;
35
+ constructor(maxBytes: number, maxSpillBytes: number | undefined, label: string, spillDir: string);
36
+ /**
37
+ * Ingest one stream chunk, counting it toward the whole-stream total. On
38
+ * first overflow of the in-memory cap a spill file is opened (when spilling
39
+ * is enabled) and every chunk (already-collected ones included) is appended
40
+ * there from then on; the in-memory tail then drops whole chunks from its
41
+ * head (or the head of a single over-cap chunk) until it fits the cap again.
42
+ * @param chunk - the raw bytes from one stream 'data' event.
43
+ */
44
+ push(chunk: Buffer): void;
45
+ /** Open the spill file lazily and append `chunk` (and any prior chunks once). */
46
+ private spillAll;
47
+ /** Stop spilling and remove the file once it can no longer hold the complete stream. */
48
+ private discardSpill;
49
+ /**
50
+ * Incremental read in whole-stream byte coordinates: returns everything
51
+ * pushed since `fromByte`. When `fromByte` has already slid out of the
52
+ * in-memory tail window, the read is `lossy` — it returns the whole
53
+ * retained tail and the gap is only recoverable from the spill file.
54
+ * @param fromByte - whole-stream offset to resume from (a prior read's `nextOffset`; 0 for the first read).
55
+ * @returns the delta text, the offset for the next read, the `lossy` flag, and the spill path when one was created.
56
+ */
57
+ readFrom(fromByte: number): {
58
+ text: string;
59
+ nextOffset: number;
60
+ lossy: boolean;
61
+ spillPath?: string;
62
+ };
63
+ /**
64
+ * Copy the retained raw tail with its position in the complete observed stream.
65
+ * @returns independent tail bytes and the total byte count before truncation.
66
+ */
67
+ snapshot(): {
68
+ bytes: Buffer;
69
+ totalBytes: number;
70
+ };
71
+ /**
72
+ * Close the spill file once the stream has ended. A failed close (delayed
73
+ * writeback fault) stops advertising the spill path — the file may be
74
+ * missing its tail — while every in-memory read keeps working. Idempotent;
75
+ * the spawn path seals both collectors at settlement so reads after exit
76
+ * never point at a still-open file.
77
+ */
78
+ seal(): void;
79
+ /**
80
+ * Seal the spill file and return the final output.
81
+ * @returns the final collected output: tail text, truncation flag, and the spill path when intact.
82
+ */
83
+ finalize(): CollectedOutput;
84
+ }
85
+ //# sourceMappingURL=output.d.ts.map
@@ -25,6 +25,8 @@ interface FileStatus {
25
25
  * is the fence a signal takes, because it reads current state instead.
26
26
  */
27
27
  export interface ProcessSnapshot {
28
+ /** Whether the process-table scan omitted no unreadable rows; absent means unverified. */
29
+ readonly complete?: boolean;
28
30
  /**
29
31
  * Return the root and its transitive descendants as observed, children first.
30
32
  * @param rootPid - tree root to descend from.
@@ -58,6 +60,7 @@ export interface ProcessInspector {
58
60
  /**
59
61
  * Read the process table once and answer tree, session, and liveness from it.
60
62
  * @returns A process-table observation whose reads are shared.
63
+ * @throws when the platform process table cannot be enumerated.
61
64
  */
62
65
  snapshot(): ProcessSnapshot;
63
66
  /**
@@ -39,7 +39,7 @@ export declare function consumeRunnerSelection(env?: NodeJS.ProcessEnv): string
39
39
  export declare function parseRunnerTargetArgv(argv: readonly string[]): string[];
40
40
  /**
41
41
  * Build direct Linux target stdio, or isolated Windows runner stdio with IPC
42
- * on fd 3 and target carriers on fd 4 through fd 6.
42
+ * on fd 3 and target carriers on fd 4 through fd 6; optional control uses fd 7.
43
43
  * @param spec - ordinary subprocess request whose stdio modes are preserved.
44
44
  * @param ipc - whether to isolate the runner and add its private Node IPC descriptor.
45
45
  * @param stdinCarrier - runner fd 4 carrier; Windows ignore passes an opened null-device fd.
@@ -62,5 +62,7 @@ export declare function resolveWindowsExecutable(command: string, cwd: string, e
62
62
  * @param spec - final target argv, cwd, and environment overrides.
63
63
  * @returns complete target environment after Node-equivalent validation.
64
64
  */
65
- export declare function targetEnvironment(spec: Pick<SubprocessSpawnSpec, 'argv' | 'cwd' | 'env'>): Record<string, string>;
65
+ export declare function targetEnvironment(spec: Pick<SubprocessSpawnSpec, 'argv' | 'cwd' | 'env'> & {
66
+ stdio?: SubprocessSpawnSpec['stdio'];
67
+ }): Record<string, string>;
66
68
  //# sourceMappingURL=runner-launch.d.ts.map
@@ -3,6 +3,7 @@
3
3
  export interface LinuxLaunchRequest {
4
4
  cwd: string;
5
5
  env: Record<string, string>;
6
+ control?: 'pipe';
6
7
  }
7
8
  /** Bounded Node-shaped error fields allowed across a private runner boundary. */
8
9
  export interface SerializedRunnerError {
@@ -22,6 +23,7 @@ export interface WindowsStartRequest {
22
23
  type: 'start';
23
24
  cwd: string;
24
25
  env: Record<string, string>;
26
+ control?: 'pipe';
25
27
  }
26
28
  /** The only parent-to-runner control message on Windows. */
27
29
  export interface WindowsTerminateRequest {
@@ -0,0 +1,37 @@
1
+ import type { SubprocessTerminalActivity, SubprocessTerminalSpawnSpec } from '@deepseek-ai/dsh-subprocess';
2
+ /** Opt-in startup integration and revision tracking for one ordinary interactive shell. */
3
+ export declare class ShellActivity {
4
+ private readonly directory;
5
+ readonly argv: readonly string[];
6
+ readonly env: Record<string, string>;
7
+ private revision;
8
+ private observed;
9
+ private invalidated;
10
+ private state;
11
+ /**
12
+ * @param directory - private startup and status file directory.
13
+ * @param argv - shell launch preserving supported user startup files.
14
+ * @param env - environment with any temporary startup redirect.
15
+ */
16
+ constructor(directory: string, argv: readonly string[], env: Record<string, string>);
17
+ /** Invalidate prompt evidence before delivering input or a foreground signal. */
18
+ invalidate(): void;
19
+ /**
20
+ * Read the latest top-level shell transition, fenced against input since that transition.
21
+ * @param pid - original shell process id.
22
+ * @returns lifecycle evidence; process ownership must be checked separately.
23
+ */
24
+ inspect(pid: number): SubprocessTerminalActivity;
25
+ /** Remove private startup and status files after process quiescence. */
26
+ dispose(): void;
27
+ private read;
28
+ }
29
+ /**
30
+ * Prepare optional Bash or Zsh integration for a plain, non-login interactive launch.
31
+ * @param spec - terminal request; custom arguments and wrapped executables remain unmodified.
32
+ * @param env - scrubbed target environment.
33
+ * @param platform - execution platform.
34
+ * @returns private integration, or undefined for unsupported launches.
35
+ */
36
+ export declare function prepareShellActivity(spec: SubprocessTerminalSpawnSpec, env: Record<string, string>, platform: NodeJS.Platform): ShellActivity | undefined;
37
+ //# sourceMappingURL=shell-activity.d.ts.map
@@ -7,7 +7,7 @@ type RunnerHost = Pick<NodeJS.Process, 'env' | 'exitCode' | 'connected' | 'cwd'
7
7
  };
8
8
  /** Injectable operations used by the protocol-owner tests. */
9
9
  export interface SpawnRunnerInternals {
10
- execve(file: string, argv: string[], env: Record<string, string>): never;
10
+ execve(file: string, argv: string[], env: Record<string, string>, control?: 'pipe'): never;
11
11
  loadWin32ProcessBindings(): CurrentTokenProcessBindings;
12
12
  spawnCurrentTokenJobProcess: typeof spawnCurrentTokenJobProcess;
13
13
  closeFileDescriptor(fileDescriptor: number): void;
@@ -8,7 +8,7 @@
8
8
  * @module dsh-subprocess-local/spawn
9
9
  */
10
10
  import { type ChildProcess, type SpawnOptions } from 'node:child_process';
11
- import type { CollectedOutput, SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
11
+ import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
12
12
  import type { ManagedProcessLaunch } from './managed-owner.ts';
13
13
  type SpawnProcess = (program: string, args: readonly string[], options: SpawnOptions) => ChildProcess;
14
14
  /**
@@ -42,79 +42,6 @@ export interface LocalSubprocessHandle extends SubprocessHandle {
42
42
  /** Force-terminate the current tree synchronously without starting timers or waits. */
43
43
  terminateForHostExit(): void;
44
44
  }
45
- /**
46
- * Prepare fallible output storage before starting a managed native process.
47
- * @param internals - optional caller-owned spill directory.
48
- * @returns binding inputs whose spill directory is ready for use.
49
- */
50
- export declare function prepareManagedProcessBinding(internals?: Pick<SpawnInternals, 'spillDir'>): {
51
- spillDir: string;
52
- };
53
- /**
54
- * Collects one stream with a bounded in-memory tail. With a spill cap, on
55
- * first overflow a spill file is created and every chunk (including those
56
- * already collected) is appended there while the full stream remains within
57
- * the cap; without one, only the in-memory tail is ever retained (the
58
- * diagnostic-tail shape — a language server's stderr).
59
- *
60
- * Tail-keep rationale (pi/OpenCode): errors and final results cluster at the
61
- * end of command output; the spill file covers the head.
62
- */
63
- export declare class OutputCollector {
64
- private readonly maxBytes;
65
- private readonly maxSpillBytes;
66
- private readonly label;
67
- private readonly spillDir;
68
- private chunks;
69
- private bytes;
70
- private dropped;
71
- private spillFd;
72
- private spillFile;
73
- private spillDisabled;
74
- /** Total bytes ever pushed (not just retained). */
75
- private total;
76
- constructor(maxBytes: number, maxSpillBytes: number | undefined, label: string, spillDir: string);
77
- /**
78
- * Ingest one stream chunk, counting it toward the whole-stream total. On
79
- * first overflow of the in-memory cap a spill file is opened (when spilling
80
- * is enabled) and every chunk (already-collected ones included) is appended
81
- * there from then on; the in-memory tail then drops whole chunks from its
82
- * head (or the head of a single over-cap chunk) until it fits the cap again.
83
- * @param chunk - the raw bytes from one stream 'data' event.
84
- */
85
- push(chunk: Buffer): void;
86
- /** Open the spill file lazily and append `chunk` (and any prior chunks once). */
87
- private spillAll;
88
- /** Stop spilling and remove the file once it can no longer hold the complete stream. */
89
- private discardSpill;
90
- /**
91
- * Incremental read in whole-stream byte coordinates: returns everything
92
- * pushed since `fromByte`. When `fromByte` has already slid out of the
93
- * in-memory tail window, the read is `lossy` — it returns the whole
94
- * retained tail and the gap is only recoverable from the spill file.
95
- * @param fromByte - whole-stream offset to resume from (a prior read's `nextOffset`; 0 for the first read).
96
- * @returns the delta text, the offset for the next read, the `lossy` flag, and the spill path when one was created.
97
- */
98
- readFrom(fromByte: number): {
99
- text: string;
100
- nextOffset: number;
101
- lossy: boolean;
102
- spillPath?: string;
103
- };
104
- /**
105
- * Close the spill file once the stream has ended. A failed close (delayed
106
- * writeback fault) stops advertising the spill path — the file may be
107
- * missing its tail — while every in-memory read keeps working. Idempotent;
108
- * the spawn path seals both collectors at settlement so reads after exit
109
- * never point at a still-open file.
110
- */
111
- seal(): void;
112
- /**
113
- * Seal the spill file and return the final output.
114
- * @returns the final collected output: tail text, truncation flag, and the spill path when intact.
115
- */
116
- finalize(): CollectedOutput;
117
- }
118
45
  /**
119
46
  * Send `sig` to a detached POSIX process group. Never throws: delivery races
120
47
  * process exit and may run in a timer callback, so failures are contained and
@@ -1,9 +1,10 @@
1
1
  /** Local node-pty terminal-process implementation for the subprocess seam. */
2
2
  import { PassThrough } from 'node:stream';
3
3
  import type { IPty } from 'node-pty';
4
- import type { SubprocessOutcome, SubprocessTerminalForeground, SubprocessTerminalHandle, SubprocessTerminalSignal } from '@deepseek-ai/dsh-subprocess';
4
+ import type { SubprocessOutcome, SubprocessTerminalActivity, SubprocessTerminalForeground, SubprocessTerminalHandle, SubprocessTerminalSignal } from '@deepseek-ai/dsh-subprocess';
5
5
  import type { BoundProcessOwner } from './managed-owner.ts';
6
6
  import type { ProcessInspector } from './process-inspector.ts';
7
+ import type { ShellActivity } from './shell-activity.ts';
7
8
  /**
8
9
  * A local terminal whose native managed range or fallback process-session
9
10
  * ownership stays below the PTY backend.
@@ -20,6 +21,9 @@ export declare class LocalTerminalHandle implements SubprocessTerminalHandle {
20
21
  private readonly platform;
21
22
  private readonly managedOwner?;
22
23
  private readonly resolveManagedOutcome?;
24
+ private readonly shellActivity?;
25
+ private readonly onQuiescence?;
26
+ private readonly observeShellExit;
23
27
  readonly pid: number;
24
28
  readonly output: PassThrough;
25
29
  readonly done: Promise<SubprocessOutcome>;
@@ -29,7 +33,12 @@ export declare class LocalTerminalHandle implements SubprocessTerminalHandle {
29
33
  private cleanup;
30
34
  private managedOwnerCleaned;
31
35
  private exited;
36
+ private outputPaused;
32
37
  private trackedDescendants;
38
+ private activityRevision;
39
+ private activityKey;
40
+ private quiescent;
41
+ private managedRangeEmpty;
33
42
  /** The spawned shell's start identity; scans stop adopting members once the root pid no longer carries it. */
34
43
  private readonly rootIdentity;
35
44
  /**
@@ -38,11 +47,13 @@ export declare class LocalTerminalHandle implements SubprocessTerminalHandle {
38
47
  * @param graceMs - TERM-to-KILL and exit-wait grace.
39
48
  * @param platform - host platform; defaults to the running platform, injectable for deterministic tests.
40
49
  */
41
- constructor(terminal: IPty, inspector: ProcessInspector, graceMs: number, platform?: NodeJS.Platform, managedOwner?: BoundProcessOwner | undefined, resolveManagedOutcome?: ((outcome: SubprocessOutcome) => SubprocessOutcome) | undefined);
50
+ constructor(terminal: IPty, inspector: ProcessInspector, graceMs: number, platform?: NodeJS.Platform, managedOwner?: BoundProcessOwner | undefined, resolveManagedOutcome?: ((outcome: SubprocessOutcome) => SubprocessOutcome) | undefined, shellActivity?: Pick<ShellActivity, "inspect" | "invalidate" | "dispose"> | undefined, onQuiescence?: (() => void) | undefined, observeShellExit?: boolean);
42
51
  /** Whether node-pty has not yet published the top-level exit event. */
43
52
  get running(): boolean;
44
53
  write(data: string): Promise<void>;
54
+ resize(cols: number, rows: number): Promise<void>;
45
55
  inspectForeground(): Promise<SubprocessTerminalForeground | undefined>;
56
+ inspectActivity(): Promise<SubprocessTerminalActivity>;
46
57
  signalForeground(signal: SubprocessTerminalSignal): Promise<number>;
47
58
  terminate(): Promise<void>;
48
59
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-subprocess-local",
3
3
  "description": "Local-subprocess implementation of the DeepSeek Harness subprocess seam",
4
- "version": "0.1.5-rc.2",
4
+ "version": "0.1.6-alpha.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -23,30 +23,36 @@
23
23
  "default": "./lib/runner.js"
24
24
  },
25
25
  "./src/*": "./src/*",
26
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./output": {
28
+ "types": "./lib/types/output.d.ts",
29
+ "default": "./lib/output.js"
30
+ }
27
31
  },
28
32
  "files": [
29
33
  "lib/index.js",
30
34
  "lib/runner.js",
31
35
  "lib/runner-*.js",
36
+ "lib/output.js",
32
37
  "scripts/ensure-spawn-helper.mjs",
33
38
  "lib/types/**/*.d.ts"
34
39
  ],
35
40
  "license": "MIT",
36
41
  "peerDependencies": {
37
- "@deepseek-ai/dsh-subprocess": "^0.1.5-rc.2",
38
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.2",
39
- "@deepseek-ai/cordis": "^4.0.2"
42
+ "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.2",
43
+ "@deepseek-ai/cordis": "^4.0.2",
44
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.2"
40
45
  },
41
46
  "dependencies": {
42
47
  "koffi": "^3.1.0",
43
48
  "node-pty": "1.2.0-beta.15",
44
- "@deepseek-ai/dsh-win32-process": "^0.1.5-rc.2"
49
+ "@deepseek-ai/dsh-lazy-require": "^0.1.6-alpha.2",
50
+ "@deepseek-ai/dsh-win32-process": "^0.1.6-alpha.2"
45
51
  },
46
52
  "devDependencies": {
47
- "@deepseek-ai/dsh-loader-smoke": "^0.1.5-rc.2",
48
- "@deepseek-ai/dsh-subprocess": "^0.1.5-rc.2",
49
- "@deepseek-ai/dsh-timeout": "^0.1.5-rc.2",
53
+ "@deepseek-ai/dsh-subprocess": "^0.1.6-alpha.2",
54
+ "@deepseek-ai/dsh-loader-smoke": "^0.1.6-alpha.2",
55
+ "@deepseek-ai/dsh-timeout": "^0.1.6-alpha.2",
50
56
  "@deepseek-ai/cordis": "^4.0.2"
51
57
  },
52
58
  "scripts": {