brl-subagent 2.3.9 → 2.5.0
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.
- package/AGENT.md +5 -0
- package/README.md +20 -8
- package/package.json +11 -9
- package/src/concurrency.ts +1 -0
- package/src/diff.ts +1 -0
- package/src/event-bus.ts +8 -2
- package/src/git.ts +68 -0
- package/src/history.ts +59 -1
- package/src/index.ts +244 -406
- package/src/kill-escalation.ts +94 -0
- package/src/logging.ts +58 -4
- package/src/messaging.ts +1 -0
- package/src/metrics.ts +1 -0
- package/src/model-availability.ts +1 -0
- package/src/notify-completion.ts +1 -0
- package/src/params.ts +1 -0
- package/src/paths.ts +1 -0
- package/src/preflight.ts +30 -14
- package/src/prelude.ts +1 -0
- package/src/presets.ts +1 -0
- package/src/proc.ts +140 -0
- package/src/prompt.ts +1 -0
- package/src/recovery-engine.ts +262 -0
- package/src/recovery.ts +325 -0
- package/src/reports.ts +64 -8
- package/src/router.ts +1 -0
- package/src/run-registry.ts +269 -0
- package/src/runner.ts +207 -16
- package/src/sanitize.ts +54 -3
- package/src/scheduler.ts +1 -0
- package/src/schema.ts +254 -0
- package/src/session-manager.ts +142 -111
- package/src/state.ts +52 -7
- package/src/templates.ts +1 -0
- package/src/transcript-path.ts +41 -0
- package/src/transcript-tail.ts +1 -0
- package/src/transcript.ts +14 -12
- package/src/tui-format.ts +1 -0
- package/src/tui.ts +231 -296
- package/src/types.ts +130 -2
- package/src/unit-run.ts +9 -0
- package/src/validate.ts +16 -2
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Purpose: The one SIGTERM → grace → SIGKILL escalation shared by foreground reap, abort/timeout, and boot recovery.
|
|
2
|
+
/**
|
|
3
|
+
* Process kill escalation (Option B U1, review items B4 + B7).
|
|
4
|
+
*
|
|
5
|
+
* The SIGTERM → grace → SIGKILL sequence was duplicated four times with
|
|
6
|
+
* inconsistent wait semantics. This module owns the ONE sequence; each site
|
|
7
|
+
* supplies its own targets and death check:
|
|
8
|
+
*
|
|
9
|
+
* - `runner.ts` `reapActiveChildren` — every tracked foreground child at once
|
|
10
|
+
* - `runner.ts` abort handler — one child
|
|
11
|
+
* - `runner.ts` timeout handler — one child
|
|
12
|
+
* - `recovery.ts` `reapPids` — every marker-verified orphan at once
|
|
13
|
+
*
|
|
14
|
+
* Order is terminate → wait → force-kill. With `pollMs` set the wait ends as
|
|
15
|
+
* soon as every target verifies dead (B4: the shutdown reap no longer blocks
|
|
16
|
+
* the full 5 s when children exit promptly), bounded by `graceMs`; without it
|
|
17
|
+
* the helper waits the full grace once (the boot scan's wait-once shape).
|
|
18
|
+
*
|
|
19
|
+
* `verifyDeath` is supplied per site but must always answer "is the process
|
|
20
|
+
* REALLY dead" — for a child, `exitCode`/`signalCode`; for a pid, `kill -0`
|
|
21
|
+
* liveness. It must never be a "a signal was delivered" check: `child.killed`
|
|
22
|
+
* flips when SIGTERM is SENT, so gating SIGKILL on it skips escalation for a
|
|
23
|
+
* SIGTERM-ignoring child (issue #303). What is shared is the
|
|
24
|
+
* terminate-then-escalate order and the no-throw-on-vanished discipline.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** A process handle the escalation helper can signal and liveness-check. */
|
|
28
|
+
export interface EscalationTarget {
|
|
29
|
+
/** Send SIGTERM; may throw when the target is already gone. */
|
|
30
|
+
terminate(): void;
|
|
31
|
+
/** Send SIGKILL; may throw when the target is already gone. */
|
|
32
|
+
forceKill(): void;
|
|
33
|
+
/** True when the target is confirmed dead (so no SIGKILL is needed). */
|
|
34
|
+
verifyDeath(): boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EscalationOptions {
|
|
38
|
+
/** Grace window between SIGTERM and SIGKILL (ms). */
|
|
39
|
+
graceMs: number;
|
|
40
|
+
/** Sleep between SIGTERM and the liveness re-check (injectable). */
|
|
41
|
+
sleep(ms: number): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Poll interval for the early-exit wait (B4). When > 0 the helper returns as
|
|
44
|
+
* soon as every target verifies dead, bounded by `graceMs`; when omitted the
|
|
45
|
+
* helper waits the full `graceMs` once.
|
|
46
|
+
*/
|
|
47
|
+
pollMs?: number;
|
|
48
|
+
/** Injectable clock for the deadline (defaults to Date.now). */
|
|
49
|
+
now?(): number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Escalate every target together: SIGTERM all, wait (at most) one grace window,
|
|
54
|
+
* then SIGKILL every target that still verifies alive. Returns the survivors.
|
|
55
|
+
* Never throws on a target that vanished mid-sequence.
|
|
56
|
+
*/
|
|
57
|
+
export async function escalateKill(
|
|
58
|
+
targets: EscalationTarget[],
|
|
59
|
+
options: EscalationOptions,
|
|
60
|
+
): Promise<EscalationTarget[]> {
|
|
61
|
+
for (const target of targets) {
|
|
62
|
+
try {
|
|
63
|
+
target.terminate();
|
|
64
|
+
} catch {
|
|
65
|
+
// Already gone — the desired end state.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (targets.length > 0) {
|
|
70
|
+
const pollMs = options.pollMs ?? 0;
|
|
71
|
+
if (pollMs > 0) {
|
|
72
|
+
const now = options.now ?? Date.now;
|
|
73
|
+
const deadline = now() + options.graceMs;
|
|
74
|
+
while (now() < deadline && !targets.every((target) => target.verifyDeath())) {
|
|
75
|
+
const remaining = Math.max(deadline - now(), 1);
|
|
76
|
+
await options.sleep(Math.min(pollMs, remaining));
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
await options.sleep(options.graceMs);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const survivors: EscalationTarget[] = [];
|
|
84
|
+
for (const target of targets) {
|
|
85
|
+
if (target.verifyDeath()) continue;
|
|
86
|
+
try {
|
|
87
|
+
target.forceKill();
|
|
88
|
+
} catch {
|
|
89
|
+
// Exited between the liveness check and the signal.
|
|
90
|
+
}
|
|
91
|
+
if (!target.verifyDeath()) survivors.push(target);
|
|
92
|
+
}
|
|
93
|
+
return survivors;
|
|
94
|
+
}
|
package/src/logging.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Purpose: Leveled structured logging with file output and rotation under `.pi/subagent-logs/`.
|
|
1
2
|
/**
|
|
2
3
|
* brl-subagent — Structured Logging (F10)
|
|
3
4
|
*
|
|
@@ -41,8 +42,49 @@ const LOG_LEVELS: Record<LogLevel, number> = {
|
|
|
41
42
|
error: 3,
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
/** The level names BRL_LOG_LEVEL accepts (the keys of LOG_LEVELS). */
|
|
46
|
+
const LOG_LEVEL_NAMES: readonly LogLevel[] = ["debug", "info", "warn", "error"];
|
|
47
|
+
|
|
44
48
|
/** Current minimum log level — can be changed at runtime */
|
|
45
|
-
let minLevel: LogLevel =
|
|
49
|
+
let minLevel: LogLevel = resolveInitialLogLevel();
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether the console mirror is enabled — opt-in via BRL_LOG_CONSOLE (issue #265).
|
|
53
|
+
* File-only is the default so extension logging never corrupts the pi TUI.
|
|
54
|
+
*/
|
|
55
|
+
let consoleOutput: boolean = resolveInitialConsoleOutput();
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Issue #235: the log verbosity knob was never wired, so the three `log.debug()`
|
|
59
|
+
* call sites were permanently suppressed with no way to turn them on.
|
|
60
|
+
* `BRL_LOG_LEVEL` is read ONCE at module load (logger call sites are spread
|
|
61
|
+
* across the codebase, and re-reading env per call would be wasteful and
|
|
62
|
+
* non-deterministic mid-run). `setLogLevel()` remains the programmatic override.
|
|
63
|
+
* Anything outside debug|info|warn|error falls back to "info".
|
|
64
|
+
*/
|
|
65
|
+
function resolveInitialLogLevel(): LogLevel {
|
|
66
|
+
const raw = process.env.BRL_LOG_LEVEL?.trim().toLowerCase();
|
|
67
|
+
if (raw && (LOG_LEVEL_NAMES as readonly string[]).includes(raw)) {
|
|
68
|
+
return raw as LogLevel;
|
|
69
|
+
}
|
|
70
|
+
return "info";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Issue #265: extension code runs inside pi's TUI process, so a raw console
|
|
75
|
+
* write lands wherever the renderer's cursor sits and corrupts the display.
|
|
76
|
+
* File-only is the default; `BRL_LOG_CONSOLE` opts the terminal mirror back in.
|
|
77
|
+
* Read ONCE at module load for the same reason as BRL_LOG_LEVEL — logger call
|
|
78
|
+
* sites are spread across the codebase. Accepts `1|true|yes`; anything else is
|
|
79
|
+
* off. `setConsoleOutput()` remains the programmatic override.
|
|
80
|
+
*
|
|
81
|
+
* Explicitly NOT gated on process.stdout.isTTY: in pi print mode stdout IS the
|
|
82
|
+
* result channel, so that test is exactly backwards.
|
|
83
|
+
*/
|
|
84
|
+
function resolveInitialConsoleOutput(): boolean {
|
|
85
|
+
const raw = process.env.BRL_LOG_CONSOLE?.trim().toLowerCase();
|
|
86
|
+
return raw === "1" || raw === "true" || raw === "yes";
|
|
87
|
+
}
|
|
46
88
|
|
|
47
89
|
// ---------------------------------------------------------------------------
|
|
48
90
|
// Logger
|
|
@@ -59,6 +101,15 @@ export function setLogLevel(level: LogLevel): void {
|
|
|
59
101
|
minLevel = level;
|
|
60
102
|
}
|
|
61
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Programmatic override for the console mirror (issue #265). Mirrors
|
|
106
|
+
* setLogLevel: module-level state, so tests can toggle it without re-reading
|
|
107
|
+
* the environment.
|
|
108
|
+
*/
|
|
109
|
+
export function setConsoleOutput(enabled: boolean): void {
|
|
110
|
+
consoleOutput = enabled;
|
|
111
|
+
}
|
|
112
|
+
|
|
62
113
|
// Issue #179 (D6): createLogger() runs at MODULE LOAD (index.ts,
|
|
63
114
|
// session-manager.ts) where the session cwd does not exist yet. The cwd is set
|
|
64
115
|
// later from the session_start hook (ctx.cwd). All logger instances share this
|
|
@@ -101,9 +152,12 @@ export function createLogger(prefix: string, cwd?: string): Logger {
|
|
|
101
152
|
|
|
102
153
|
const line = JSON.stringify(entry);
|
|
103
154
|
|
|
104
|
-
// Console
|
|
105
|
-
|
|
106
|
-
|
|
155
|
+
// Console mirror — opt-in (issue #265). Off by default so a logger call
|
|
156
|
+
// can never corrupt the pi TUI renderer; BRL_LOG_CONSOLE opts back in.
|
|
157
|
+
if (consoleOutput) {
|
|
158
|
+
const consoleMethod = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
|
|
159
|
+
consoleMethod(`[${prefix}] ${level.toUpperCase()}: ${message}`);
|
|
160
|
+
}
|
|
107
161
|
|
|
108
162
|
// File output — resolved on each call because the cwd may be set after
|
|
109
163
|
// createLogger ran (module load) but before the first entry.
|
package/src/messaging.ts
CHANGED
package/src/metrics.ts
CHANGED
package/src/notify-completion.ts
CHANGED
package/src/params.ts
CHANGED
package/src/paths.ts
CHANGED
package/src/preflight.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Purpose: Pre-spawn environment checks: pi binary, temp-dir writability, cwd readability.
|
|
1
2
|
/**
|
|
2
3
|
* brl-subagent — Pre-flight Checks (R3)
|
|
3
4
|
*
|
|
@@ -21,6 +22,33 @@ export type PreflightResult = { ok: true } | { ok: false; error: string };
|
|
|
21
22
|
// Checks
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Walk PATH for an executable `pi`, returning the first match.
|
|
27
|
+
*
|
|
28
|
+
* Mirrors the spawn-time fallback in `getPiInvocation` (the bare `"pi"`
|
|
29
|
+
* case). Shared with the Tier-2 e2e harness (issue #271) so the preflight
|
|
30
|
+
* check and the harness resolve the real binary identically.
|
|
31
|
+
*/
|
|
32
|
+
export function resolvePiOnPath(): string | undefined {
|
|
33
|
+
const pathDirs = (process.env.PATH || "").split(path.delimiter);
|
|
34
|
+
const isWindows = process.platform === "win32";
|
|
35
|
+
const piNames = isWindows ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
|
|
36
|
+
|
|
37
|
+
for (const dir of pathDirs) {
|
|
38
|
+
for (const name of piNames) {
|
|
39
|
+
const fullPath = path.join(dir, name);
|
|
40
|
+
try {
|
|
41
|
+
fs.accessSync(fullPath, fs.constants.X_OK);
|
|
42
|
+
return fullPath;
|
|
43
|
+
} catch {
|
|
44
|
+
// Not found in this directory — continue searching
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
24
52
|
/**
|
|
25
53
|
* Verify the pi binary is accessible via the same resolution logic used
|
|
26
54
|
* at spawn time. When `getPiInvocation` falls back to "pi" on PATH, we
|
|
@@ -44,20 +72,8 @@ function checkPiBinary(): PreflightResult {
|
|
|
44
72
|
}
|
|
45
73
|
|
|
46
74
|
// "pi" fallback — search PATH
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const piNames = isWindows ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
|
|
50
|
-
|
|
51
|
-
for (const dir of pathDirs) {
|
|
52
|
-
for (const name of piNames) {
|
|
53
|
-
const fullPath = path.join(dir, name);
|
|
54
|
-
try {
|
|
55
|
-
fs.accessSync(fullPath, fs.constants.X_OK);
|
|
56
|
-
return { ok: true };
|
|
57
|
-
} catch {
|
|
58
|
-
// Not found in this directory — continue searching
|
|
59
|
-
}
|
|
60
|
-
}
|
|
75
|
+
if (resolvePiOnPath()) {
|
|
76
|
+
return { ok: true };
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
return {
|
package/src/prelude.ts
CHANGED
package/src/presets.ts
CHANGED
package/src/proc.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Purpose: Production process primitives — Linux/POSIX /proc reads, feature detection, and the default recovery deps.
|
|
2
|
+
/**
|
|
3
|
+
* Option B U1 — production process wiring for the recovery engine.
|
|
4
|
+
*
|
|
5
|
+
* The recovery decision engine (`src/recovery-engine.ts`) is pure over the
|
|
6
|
+
* injected `RecoveryDeps`. This module supplies the REAL deps: the Linux/POSIX
|
|
7
|
+
* `/proc` start-time token (`/proc/<pid>/stat` field 22), the child-marker scan
|
|
8
|
+
* (`/proc/<pid>/environ`), cross-platform pid liveness, and the default
|
|
9
|
+
* `RecoveryDeps` assembly.
|
|
10
|
+
*
|
|
11
|
+
* PLATFORM BOUNDARY (required): the start token and marker scan are Linux/POSIX
|
|
12
|
+
* reads. `isProcAvailable()` feature-detects them (cached; the answer cannot
|
|
13
|
+
* change mid-process). Where they are unavailable, `pidAlive` still works but
|
|
14
|
+
* on its own cannot rule out pid reuse — so the boot scan skips entirely and
|
|
15
|
+
* nothing is killed or marked unverified (`recoverProduction`, src/recovery.ts).
|
|
16
|
+
*
|
|
17
|
+
* `__setProcAvailableForTest` is the injectable seam that lets a test exercise
|
|
18
|
+
* that platform guard without mocking `node:fs` module-wide.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
import * as path from "node:path";
|
|
23
|
+
import type { ProcessOwner } from "./types";
|
|
24
|
+
import { SIGKILL_GRACE_MS } from "./types";
|
|
25
|
+
import { CHILD_MARKER_ENV_KEY } from "./sanitize";
|
|
26
|
+
import type { RecoveryDeps } from "./recovery-engine";
|
|
27
|
+
|
|
28
|
+
const PROC_ROOT = "/proc";
|
|
29
|
+
|
|
30
|
+
let procAvailable: boolean | undefined;
|
|
31
|
+
let procAvailableOverride: boolean | undefined;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* TEST-ONLY: force the `/proc` feature-detection answer (undefined restores the
|
|
35
|
+
* real detection and clears the cache). The production scan consults this
|
|
36
|
+
* through `isProcAvailable`, so the platform guard is exercisable in-repo.
|
|
37
|
+
*/
|
|
38
|
+
export function __setProcAvailableForTest(value: boolean | undefined): void {
|
|
39
|
+
procAvailableOverride = value;
|
|
40
|
+
procAvailable = undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Feature-detect the Linux/POSIX `/proc` surface this module needs. Cached:
|
|
45
|
+
* the answer cannot change mid-process. Exported so the entry point can skip
|
|
46
|
+
* the whole scan with one log line on platforms without /proc.
|
|
47
|
+
*/
|
|
48
|
+
export function isProcAvailable(): boolean {
|
|
49
|
+
if (procAvailableOverride !== undefined) return procAvailableOverride;
|
|
50
|
+
if (procAvailable === undefined) {
|
|
51
|
+
try {
|
|
52
|
+
procAvailable = fs.existsSync(path.join(PROC_ROOT, "self", "stat"));
|
|
53
|
+
} catch {
|
|
54
|
+
procAvailable = false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return procAvailable;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Read a process's start-time token — `/proc/<pid>/stat` field 22 (1-indexed),
|
|
62
|
+
* i.e. index 19 of the whitespace-split fields AFTER the `(comm)` field. The
|
|
63
|
+
* comm field is skipped by slicing at the LAST `)` so a name containing spaces
|
|
64
|
+
* or parens cannot shift the parse. Returns undefined when /proc is
|
|
65
|
+
* unavailable, the pid is invalid, or the process vanished.
|
|
66
|
+
*/
|
|
67
|
+
export function readStartToken(pid: number): string | undefined {
|
|
68
|
+
if (!isProcAvailable() || !Number.isInteger(pid) || pid <= 0) return undefined;
|
|
69
|
+
try {
|
|
70
|
+
const stat = fs.readFileSync(path.join(PROC_ROOT, String(pid), "stat"), "utf-8");
|
|
71
|
+
const close = stat.lastIndexOf(")");
|
|
72
|
+
if (close < 0) return undefined;
|
|
73
|
+
const rest = stat.slice(close + 1).trim();
|
|
74
|
+
if (!rest) return undefined;
|
|
75
|
+
const fields = rest.split(/\s+/);
|
|
76
|
+
const token = fields[19];
|
|
77
|
+
return token && /^\d+$/.test(token) ? token : undefined;
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The CURRENT process's owner identity (pid + start token; empty string when unavailable). */
|
|
84
|
+
export function currentProcessOwner(): ProcessOwner {
|
|
85
|
+
return { pid: process.pid, start: readStartToken(process.pid) ?? "" };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Cross-platform pid liveness (`kill -0`). EPERM means the process exists. */
|
|
89
|
+
export function pidAlive(pid: number): boolean {
|
|
90
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
91
|
+
try {
|
|
92
|
+
process.kill(pid, 0);
|
|
93
|
+
return true;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return (err as NodeJS.ErrnoException).code === "EPERM";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pids whose environment carries the marker. Scans `/proc/<pid>/environ` (NUL
|
|
101
|
+
* separated). Skips this process; unreadable/vanished processes are ignored.
|
|
102
|
+
* Returns [] — never a kill — when /proc is unavailable.
|
|
103
|
+
*/
|
|
104
|
+
export function findByMarker(marker: string): number[] {
|
|
105
|
+
if (!marker || !isProcAvailable()) return [];
|
|
106
|
+
const needle = `${CHILD_MARKER_ENV_KEY}=${marker}`;
|
|
107
|
+
const found: number[] = [];
|
|
108
|
+
let entries: string[];
|
|
109
|
+
try {
|
|
110
|
+
entries = fs.readdirSync(PROC_ROOT);
|
|
111
|
+
} catch {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
116
|
+
const pid = Number(entry);
|
|
117
|
+
if (pid === process.pid) continue;
|
|
118
|
+
try {
|
|
119
|
+
const environ = fs.readFileSync(path.join(PROC_ROOT, entry, "environ"), "utf-8");
|
|
120
|
+
if (environ.split("\0").includes(needle)) found.push(pid);
|
|
121
|
+
} catch {
|
|
122
|
+
// Vanished or not readable by this user — not a verified match.
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return found;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The real production dependencies (Linux/POSIX /proc reads + process signals). */
|
|
129
|
+
export function defaultRecoveryDeps(): RecoveryDeps {
|
|
130
|
+
return {
|
|
131
|
+
pidAlive,
|
|
132
|
+
startTokenOf: readStartToken,
|
|
133
|
+
findByMarker,
|
|
134
|
+
kill: (pid, signal) => {
|
|
135
|
+
process.kill(pid, signal);
|
|
136
|
+
},
|
|
137
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
138
|
+
graceMs: SIGKILL_GRACE_MS,
|
|
139
|
+
};
|
|
140
|
+
}
|
package/src/prompt.ts
CHANGED