@aria-cli/tools 1.0.9 → 1.0.11
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/package.json +9 -5
- package/src/__tests__/web-fetch-download.test.ts +0 -433
- package/src/__tests__/web-tools.test.ts +0 -619
- package/src/ask-user-interaction.ts +0 -33
- package/src/cache/web-cache.ts +0 -110
- package/src/definitions/arion.ts +0 -118
- package/src/definitions/browser/browser.ts +0 -502
- package/src/definitions/browser/index.ts +0 -5
- package/src/definitions/browser/pw-downloads.ts +0 -142
- package/src/definitions/browser/pw-interactions.ts +0 -282
- package/src/definitions/browser/pw-responses.ts +0 -98
- package/src/definitions/browser/pw-session.ts +0 -405
- package/src/definitions/browser/pw-shared.ts +0 -85
- package/src/definitions/browser/pw-snapshot.ts +0 -383
- package/src/definitions/browser/pw-state.ts +0 -101
- package/src/definitions/browser/types.ts +0 -203
- package/src/definitions/code-intelligence.ts +0 -526
- package/src/definitions/core.ts +0 -118
- package/src/definitions/delegation.ts +0 -567
- package/src/definitions/deploy.ts +0 -73
- package/src/definitions/filesystem.ts +0 -217
- package/src/definitions/frg.ts +0 -67
- package/src/definitions/index.ts +0 -28
- package/src/definitions/memory.ts +0 -150
- package/src/definitions/messaging.ts +0 -734
- package/src/definitions/meta.ts +0 -392
- package/src/definitions/network.ts +0 -179
- package/src/definitions/outlook.ts +0 -318
- package/src/definitions/patch/apply-patch.ts +0 -235
- package/src/definitions/patch/fuzzy-match.ts +0 -217
- package/src/definitions/patch/index.ts +0 -1
- package/src/definitions/patch/patch-parser.ts +0 -297
- package/src/definitions/patch/sandbox-paths.ts +0 -129
- package/src/definitions/process/index.ts +0 -5
- package/src/definitions/process/process-registry.ts +0 -303
- package/src/definitions/process/process.ts +0 -456
- package/src/definitions/process/pty-keys.ts +0 -298
- package/src/definitions/process/session-slug.ts +0 -147
- package/src/definitions/quip.ts +0 -225
- package/src/definitions/search.ts +0 -67
- package/src/definitions/session-history.ts +0 -79
- package/src/definitions/shell.ts +0 -202
- package/src/definitions/slack.ts +0 -211
- package/src/definitions/web.ts +0 -119
- package/src/executors/apply-patch.ts +0 -1035
- package/src/executors/arion.ts +0 -199
- package/src/executors/code-intelligence.ts +0 -1179
- package/src/executors/deploy.ts +0 -1066
- package/src/executors/filesystem.ts +0 -1428
- package/src/executors/frg-freshness.ts +0 -743
- package/src/executors/frg.ts +0 -394
- package/src/executors/index.ts +0 -280
- package/src/executors/learning-meta.ts +0 -1367
- package/src/executors/lsp-client.ts +0 -355
- package/src/executors/memory.ts +0 -978
- package/src/executors/meta.ts +0 -293
- package/src/executors/process-registry.ts +0 -570
- package/src/executors/pty-session-store.ts +0 -43
- package/src/executors/pty.ts +0 -342
- package/src/executors/restart.ts +0 -133
- package/src/executors/search-freshness.ts +0 -249
- package/src/executors/search-types.ts +0 -98
- package/src/executors/search.ts +0 -89
- package/src/executors/self-diagnose.ts +0 -552
- package/src/executors/session-history.ts +0 -435
- package/src/executors/shell-safety.ts +0 -519
- package/src/executors/shell.ts +0 -1243
- package/src/executors/utils.ts +0 -40
- package/src/executors/web.ts +0 -786
- package/src/extraction/content-extraction.ts +0 -281
- package/src/extraction/index.ts +0 -5
- package/src/headless-control-contract.ts +0 -1149
- package/src/index.ts +0 -788
- package/src/local-control-http-auth.ts +0 -2
- package/src/mcp/client.ts +0 -218
- package/src/mcp/connection.ts +0 -568
- package/src/mcp/index.ts +0 -11
- package/src/mcp/jsonrpc.ts +0 -195
- package/src/mcp/types.ts +0 -199
- package/src/network-control-adapter.ts +0 -88
- package/src/network-runtime/address-types.ts +0 -218
- package/src/network-runtime/db-owner-fencing.ts +0 -91
- package/src/network-runtime/delivery-receipts.ts +0 -372
- package/src/network-runtime/direct-endpoint-authority.ts +0 -35
- package/src/network-runtime/index.ts +0 -316
- package/src/network-runtime/local-control-contract.ts +0 -784
- package/src/network-runtime/node-store-contract.ts +0 -46
- package/src/network-runtime/pair-route-contract.ts +0 -97
- package/src/network-runtime/peer-capabilities.ts +0 -48
- package/src/network-runtime/peer-principal-ref.ts +0 -20
- package/src/network-runtime/peer-state-machine.ts +0 -160
- package/src/network-runtime/protocol-schemas.ts +0 -265
- package/src/network-runtime/runtime-bootstrap-contract.ts +0 -83
- package/src/outlook/desktop-session.ts +0 -409
- package/src/policy.ts +0 -171
- package/src/providers/brave.ts +0 -80
- package/src/providers/duckduckgo.ts +0 -199
- package/src/providers/exa.ts +0 -85
- package/src/providers/firecrawl.ts +0 -77
- package/src/providers/index.ts +0 -8
- package/src/providers/jina.ts +0 -70
- package/src/providers/router.ts +0 -121
- package/src/providers/search-provider.ts +0 -74
- package/src/providers/tavily.ts +0 -74
- package/src/quip/desktop-session.ts +0 -435
- package/src/registry/index.ts +0 -1
- package/src/registry/registry.ts +0 -905
- package/src/runtime-socket-local-control-client.ts +0 -632
- package/src/security/dns-normalization.ts +0 -34
- package/src/security/dns-pinning.ts +0 -138
- package/src/security/external-content.ts +0 -129
- package/src/security/ssrf.ts +0 -207
- package/src/slack/desktop-session.ts +0 -493
- package/src/tool-factory.ts +0 -91
- package/src/types.ts +0 -1341
- package/src/utils/retry.ts +0 -163
- package/src/utils/safe-parse-json.ts +0 -176
- package/src/utils/url.ts +0 -20
- package/tests/benchmarks/registry.bench.ts +0 -57
- package/tests/cache/web-cache.test.ts +0 -147
- package/tests/critical-integration.test.ts +0 -1465
- package/tests/definitions/apply-patch.test.ts +0 -586
- package/tests/definitions/browser.test.ts +0 -495
- package/tests/definitions/delegation-pause-resume.test.ts +0 -758
- package/tests/definitions/execution.test.ts +0 -671
- package/tests/definitions/messaging-inbox-scope.test.ts +0 -229
- package/tests/definitions/messaging.test.ts +0 -1468
- package/tests/definitions/outlook.test.ts +0 -30
- package/tests/definitions/process.test.ts +0 -469
- package/tests/definitions/slack.test.ts +0 -28
- package/tests/definitions/tool-inventory.test.ts +0 -218
- package/tests/e2e/delegation-quest-orchestration.e2e.test.ts +0 -433
- package/tests/e2e/memory-tool-discovery-contract.e2e.test.ts +0 -81
- package/tests/executors/apply-patch.test.ts +0 -538
- package/tests/executors/arion.test.ts +0 -309
- package/tests/executors/conversation-primitives.test.ts +0 -250
- package/tests/executors/deploy.test.ts +0 -746
- package/tests/executors/filesystem-tools.test.ts +0 -357
- package/tests/executors/filesystem.test.ts +0 -959
- package/tests/executors/frg-freshness.test.ts +0 -136
- package/tests/executors/frg-merge.test.ts +0 -70
- package/tests/executors/frg-session-content.test.ts +0 -40
- package/tests/executors/frg.test.ts +0 -56
- package/tests/executors/memory-bugfixes.test.ts +0 -257
- package/tests/executors/memory-real-memoria.integration.test.ts +0 -316
- package/tests/executors/memory.test.ts +0 -853
- package/tests/executors/meta-tools.test.ts +0 -411
- package/tests/executors/meta.test.ts +0 -683
- package/tests/executors/path-containment.test.ts +0 -51
- package/tests/executors/process-registry.test.ts +0 -505
- package/tests/executors/pty.test.ts +0 -664
- package/tests/executors/quest-security.test.ts +0 -249
- package/tests/executors/read-file-media.test.ts +0 -230
- package/tests/executors/recall-knowledge-schema.test.ts +0 -209
- package/tests/executors/recall-tags.test.ts +0 -278
- package/tests/executors/remember-null-safety.contract.test.ts +0 -41
- package/tests/executors/restart.test.ts +0 -67
- package/tests/executors/search-unified.test.ts +0 -381
- package/tests/executors/session-history.test.ts +0 -340
- package/tests/executors/session-transcript.test.ts +0 -561
- package/tests/executors/shell-abort.test.ts +0 -416
- package/tests/executors/shell-env-blocklist.test.ts +0 -648
- package/tests/executors/shell-env-process.test.ts +0 -245
- package/tests/executors/shell-process-registry.test.ts +0 -334
- package/tests/executors/shell-tools.test.ts +0 -393
- package/tests/executors/shell.test.ts +0 -690
- package/tests/executors/web-abort-vs-timeout.test.ts +0 -213
- package/tests/executors/web-integration.test.ts +0 -633
- package/tests/executors/web-symlink.test.ts +0 -18
- package/tests/executors/web.test.ts +0 -1400
- package/tests/executors/write-stdin.test.ts +0 -145
- package/tests/extraction/content-extraction.test.ts +0 -153
- package/tests/guards/tools-default-test-lane.integration.test.ts +0 -21
- package/tests/guards/tools-package-test-commands.e2e.test.ts +0 -43
- package/tests/guards/tools-test-lane-manifest.contract.test.ts +0 -76
- package/tests/guards/tools-vitest-workspace-alias.contract.test.ts +0 -63
- package/tests/helpers/async-waits.ts +0 -53
- package/tests/integration/headless-control-contract.integration.test.ts +0 -153
- package/tests/integration/memory-tool-schema-parity.integration.test.ts +0 -67
- package/tests/integration/meta-tools-round-trip.integration.test.ts +0 -506
- package/tests/integration/quest-round-trip.test.ts +0 -303
- package/tests/integration/registry-executor-flow.test.ts +0 -85
- package/tests/integration.test.ts +0 -177
- package/tests/loading-tier.test.ts +0 -126
- package/tests/mcp/client-reconnect.test.ts +0 -267
- package/tests/mcp/connection.test.ts +0 -846
- package/tests/mcp/injectable-logger.test.ts +0 -83
- package/tests/mcp/jsonrpc.test.ts +0 -109
- package/tests/mcp/lifecycle.test.ts +0 -879
- package/tests/network-runtime/address-types.contract.test.ts +0 -143
- package/tests/network-runtime/continuity-bind-schema.contract.test.ts +0 -203
- package/tests/network-runtime/local-control-contract.test.ts +0 -869
- package/tests/network-runtime/local-control-invite-token.contract.test.ts +0 -146
- package/tests/network-runtime/node-store-contract.test.ts +0 -11
- package/tests/network-runtime/pair-protocol-nodeid.contract.test.ts +0 -15
- package/tests/network-runtime/peer-state-machine.contract.test.ts +0 -148
- package/tests/network-runtime/protocol-schemas.contract.test.ts +0 -512
- package/tests/network-runtime/relay-pending-nodeid.contract.test.ts +0 -62
- package/tests/network-runtime/runtime-bootstrap-contract.test.ts +0 -227
- package/tests/network-runtime/runtime-socket-local-control-client.test.ts +0 -621
- package/tests/network-runtime/wait-for-message-script.test.ts +0 -288
- package/tests/parallel.test.ts +0 -71
- package/tests/policy.test.ts +0 -184
- package/tests/print-default-test-lane.ts +0 -14
- package/tests/print-test-lane-manifest.ts +0 -22
- package/tests/providers/brave.test.ts +0 -159
- package/tests/providers/duckduckgo.test.ts +0 -207
- package/tests/providers/exa.test.ts +0 -175
- package/tests/providers/firecrawl.test.ts +0 -168
- package/tests/providers/jina.test.ts +0 -144
- package/tests/providers/router.test.ts +0 -328
- package/tests/providers/tavily.test.ts +0 -165
- package/tests/registry/discovery.test.ts +0 -154
- package/tests/registry/injectable-logger.test.ts +0 -230
- package/tests/registry/input-validation.test.ts +0 -361
- package/tests/registry/interface-completeness.test.ts +0 -85
- package/tests/registry/mcp-integration.test.ts +0 -103
- package/tests/registry/mcp-read-only-hint.test.ts +0 -60
- package/tests/registry/memoria-discovery.test.ts +0 -390
- package/tests/registry/nested-validation.test.ts +0 -283
- package/tests/registry/pseudo-tool-filtering.test.ts +0 -258
- package/tests/registry/registration-lifecycle.test.ts +0 -133
- package/tests/registry-validation.test.ts +0 -424
- package/tests/registry.test.ts +0 -460
- package/tests/security/dns-pinning.test.ts +0 -162
- package/tests/security/external-content.test.ts +0 -144
- package/tests/security/ssrf.test.ts +0 -118
- package/tests/shell-safety-integration.test.ts +0 -32
- package/tests/shell-safety.test.ts +0 -365
- package/tests/slack/desktop-session.test.ts +0 -50
- package/tests/test-lane-manifest.ts +0 -440
- package/tests/test-utils.ts +0 -27
- package/tests/tool-factory.test.ts +0 -188
- package/tests/utils/retry.test.ts +0 -231
- package/tests/utils/url.test.ts +0 -63
- package/tsconfig.cjs.json +0 -24
- package/tsconfig.json +0 -12
- package/vitest.config.ts +0 -55
- package/vitest.e2e.config.ts +0 -24
- package/vitest.integration.config.ts +0 -24
- package/vitest.native.config.ts +0 -24
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @aria/tools - Spawned Process Registry
|
|
3
|
-
*
|
|
4
|
-
* Tracks PIDs of child processes spawned by shell executors.
|
|
5
|
-
* Allows bulk cleanup (SIGTERM then SIGKILL) when a RunSession closes,
|
|
6
|
-
* preventing orphaned processes from lingering.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* How long to wait after SIGTERM before escalating to SIGKILL (ms).
|
|
11
|
-
*/
|
|
12
|
-
const SIGKILL_TIMEOUT_MS = 2000;
|
|
13
|
-
const POST_KILL_POLL_INTERVAL_MS = 25;
|
|
14
|
-
const PROCESS_REAP_POLL_INTERVAL_MS = 250;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Max number of exited-process snapshots to retain for observability.
|
|
18
|
-
*/
|
|
19
|
-
const EXIT_HISTORY_LIMIT = 100;
|
|
20
|
-
|
|
21
|
-
export interface TrackedProcessMetadata {
|
|
22
|
-
command?: string;
|
|
23
|
-
args?: string[];
|
|
24
|
-
cwd?: string;
|
|
25
|
-
interactive?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface ProcessExitMetadata {
|
|
29
|
-
exitCode?: number | null;
|
|
30
|
-
signal?: string | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ProcessInfoSnapshot {
|
|
34
|
-
pid: number;
|
|
35
|
-
command: string | null;
|
|
36
|
-
args: string[];
|
|
37
|
-
cwd: string | null;
|
|
38
|
-
interactive: boolean;
|
|
39
|
-
startedAt: string;
|
|
40
|
-
runtimeMs: number;
|
|
41
|
-
status: "running" | "exited";
|
|
42
|
-
exitCode: number | null;
|
|
43
|
-
signal: string | null;
|
|
44
|
-
endedAt: string | null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ProcessWaitResult {
|
|
48
|
-
pid: number;
|
|
49
|
-
status: "running" | "exited" | "not_found";
|
|
50
|
-
timedOut: boolean;
|
|
51
|
-
process?: ProcessInfoSnapshot;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface ActiveProcessRecord {
|
|
55
|
-
pid: number;
|
|
56
|
-
command: string | null;
|
|
57
|
-
args: string[];
|
|
58
|
-
cwd: string | null;
|
|
59
|
-
interactive: boolean;
|
|
60
|
-
reapOnExit: boolean;
|
|
61
|
-
deferredExitCode: number | null;
|
|
62
|
-
deferredSignal: string | null;
|
|
63
|
-
startedAtMs: number;
|
|
64
|
-
startedAt: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
interface ExitedProcessRecord extends ActiveProcessRecord {
|
|
68
|
-
exitCode: number | null;
|
|
69
|
-
signal: string | null;
|
|
70
|
-
endedAtMs: number;
|
|
71
|
-
endedAt: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Registry that tracks spawned child process PIDs.
|
|
76
|
-
*
|
|
77
|
-
* Usage:
|
|
78
|
-
* - Shell executors call `add(pid)` after spawning a process
|
|
79
|
-
* - Shell executors call `remove(pid)` when a process exits normally
|
|
80
|
-
* - RunSession calls `killAll()` during close() to clean up orphans
|
|
81
|
-
*/
|
|
82
|
-
export class SpawnedProcessRegistry {
|
|
83
|
-
private readonly active = new Map<number, ActiveProcessRecord>();
|
|
84
|
-
private readonly exited = new Map<number, ExitedProcessRecord>();
|
|
85
|
-
private readonly waiters = new Map<number, Array<(record: ExitedProcessRecord) => void>>();
|
|
86
|
-
private readonly reapers = new Map<number, NodeJS.Timeout>();
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Register a spawned process PID.
|
|
90
|
-
*/
|
|
91
|
-
add(pid: number, metadata: TrackedProcessMetadata = {}): void {
|
|
92
|
-
const existing = this.active.get(pid);
|
|
93
|
-
if (existing) {
|
|
94
|
-
this.active.set(pid, {
|
|
95
|
-
...existing,
|
|
96
|
-
command: metadata.command ?? existing.command,
|
|
97
|
-
args: metadata.args ? [...metadata.args] : existing.args,
|
|
98
|
-
cwd: metadata.cwd ?? existing.cwd,
|
|
99
|
-
interactive: metadata.interactive ?? existing.interactive,
|
|
100
|
-
});
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const startedAtMs = Date.now();
|
|
105
|
-
this.active.set(pid, {
|
|
106
|
-
pid,
|
|
107
|
-
command: metadata.command ?? null,
|
|
108
|
-
args: metadata.args ? [...metadata.args] : [],
|
|
109
|
-
cwd: metadata.cwd ?? null,
|
|
110
|
-
interactive: metadata.interactive ?? false,
|
|
111
|
-
reapOnExit: isTrackedProcessAlive(pid),
|
|
112
|
-
deferredExitCode: null,
|
|
113
|
-
deferredSignal: null,
|
|
114
|
-
startedAtMs,
|
|
115
|
-
startedAt: new Date(startedAtMs).toISOString(),
|
|
116
|
-
});
|
|
117
|
-
this.exited.delete(pid);
|
|
118
|
-
this.ensureReaper(pid);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Deregister a process PID (e.g., after normal exit).
|
|
123
|
-
*/
|
|
124
|
-
remove(pid: number, exit: ProcessExitMetadata = {}): void {
|
|
125
|
-
const record = this.active.get(pid);
|
|
126
|
-
if (!record) {
|
|
127
|
-
const exitedRecord = this.exited.get(pid);
|
|
128
|
-
if (exitedRecord) {
|
|
129
|
-
this.exited.set(pid, {
|
|
130
|
-
...exitedRecord,
|
|
131
|
-
exitCode: exitedRecord.exitCode ?? normalizeExitCode(exit.exitCode),
|
|
132
|
-
signal:
|
|
133
|
-
exitedRecord.exitCode !== null
|
|
134
|
-
? exitedRecord.signal
|
|
135
|
-
: (exitedRecord.signal ?? normalizeSignal(exit.signal)),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
this.clearReaper(pid);
|
|
142
|
-
this.active.delete(pid);
|
|
143
|
-
|
|
144
|
-
const endedAtMs = Date.now();
|
|
145
|
-
const recordedExitCode = normalizeExitCode(exit.exitCode) ?? record.deferredExitCode;
|
|
146
|
-
const exitedRecord: ExitedProcessRecord = {
|
|
147
|
-
...record,
|
|
148
|
-
exitCode: recordedExitCode,
|
|
149
|
-
signal: record.deferredSignal ?? normalizeSignal(exit.signal),
|
|
150
|
-
endedAtMs,
|
|
151
|
-
endedAt: new Date(endedAtMs).toISOString(),
|
|
152
|
-
};
|
|
153
|
-
this.exited.set(pid, exitedRecord);
|
|
154
|
-
this.pruneExitedHistory();
|
|
155
|
-
|
|
156
|
-
const waiters = this.waiters.get(pid);
|
|
157
|
-
if (waiters && waiters.length > 0) {
|
|
158
|
-
this.waiters.delete(pid);
|
|
159
|
-
for (const waiter of waiters) {
|
|
160
|
-
waiter(exitedRecord);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Check whether a PID is tracked.
|
|
167
|
-
*/
|
|
168
|
-
has(pid: number): boolean {
|
|
169
|
-
return this.active.has(pid);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
recordExitMetadata(pid: number, exit: ProcessExitMetadata = {}): void {
|
|
173
|
-
const record = this.active.get(pid);
|
|
174
|
-
if (!record) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
record.deferredExitCode = normalizeExitCode(exit.exitCode);
|
|
178
|
-
record.deferredSignal = normalizeSignal(exit.signal);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Number of tracked PIDs.
|
|
183
|
-
*/
|
|
184
|
-
get size(): number {
|
|
185
|
-
return this.active.size;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Return a snapshot of all tracked PIDs.
|
|
190
|
-
*/
|
|
191
|
-
getAll(): number[] {
|
|
192
|
-
return [...this.active.keys()];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Return process snapshots for observability.
|
|
197
|
-
*/
|
|
198
|
-
listProcesses(options: { includeExited?: boolean } = {}): ProcessInfoSnapshot[] {
|
|
199
|
-
const now = Date.now();
|
|
200
|
-
const snapshots: ProcessInfoSnapshot[] = [];
|
|
201
|
-
for (const pid of [...this.active.keys()]) {
|
|
202
|
-
this.reapIfExited(pid);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
for (const record of this.active.values()) {
|
|
206
|
-
snapshots.push(this.toRunningSnapshot(record, now));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (options.includeExited) {
|
|
210
|
-
for (const record of this.exited.values()) {
|
|
211
|
-
snapshots.push(this.toExitedSnapshot(record));
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return snapshots.sort((a, b) => a.pid - b.pid);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Return a single process snapshot by PID.
|
|
220
|
-
*/
|
|
221
|
-
getProcess(pid: number): ProcessInfoSnapshot | undefined {
|
|
222
|
-
this.reapIfExited(pid);
|
|
223
|
-
const activeRecord = this.active.get(pid);
|
|
224
|
-
if (activeRecord) {
|
|
225
|
-
return this.toRunningSnapshot(activeRecord);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const exitedRecord = this.exited.get(pid);
|
|
229
|
-
if (exitedRecord) {
|
|
230
|
-
return this.toExitedSnapshot(exitedRecord);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Wait for a tracked process to exit, up to timeoutMs.
|
|
238
|
-
*/
|
|
239
|
-
async waitForExit(pid: number, timeoutMs = 30_000): Promise<ProcessWaitResult> {
|
|
240
|
-
this.reapIfExited(pid);
|
|
241
|
-
const exitedRecord = this.exited.get(pid);
|
|
242
|
-
if (exitedRecord) {
|
|
243
|
-
return {
|
|
244
|
-
pid,
|
|
245
|
-
status: "exited",
|
|
246
|
-
timedOut: false,
|
|
247
|
-
process: this.toExitedSnapshot(exitedRecord),
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const activeRecord = this.active.get(pid);
|
|
252
|
-
if (!activeRecord) {
|
|
253
|
-
return {
|
|
254
|
-
pid,
|
|
255
|
-
status: "not_found",
|
|
256
|
-
timedOut: false,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (timeoutMs <= 0) {
|
|
261
|
-
return {
|
|
262
|
-
pid,
|
|
263
|
-
status: "running",
|
|
264
|
-
timedOut: true,
|
|
265
|
-
process: this.toRunningSnapshot(activeRecord),
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return new Promise((resolve) => {
|
|
270
|
-
let settled = false;
|
|
271
|
-
|
|
272
|
-
const settle = (result: ProcessWaitResult): void => {
|
|
273
|
-
if (settled) return;
|
|
274
|
-
settled = true;
|
|
275
|
-
resolve(result);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const removeWaiter = (target: (record: ExitedProcessRecord) => void): void => {
|
|
279
|
-
const existing = this.waiters.get(pid);
|
|
280
|
-
if (!existing) return;
|
|
281
|
-
const next = existing.filter((entry) => entry !== target);
|
|
282
|
-
if (next.length === 0) {
|
|
283
|
-
this.waiters.delete(pid);
|
|
284
|
-
} else {
|
|
285
|
-
this.waiters.set(pid, next);
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
const waiter = (record: ExitedProcessRecord): void => {
|
|
290
|
-
clearTimeout(timeoutId);
|
|
291
|
-
removeWaiter(waiter);
|
|
292
|
-
settle({
|
|
293
|
-
pid,
|
|
294
|
-
status: "exited",
|
|
295
|
-
timedOut: false,
|
|
296
|
-
process: this.toExitedSnapshot(record),
|
|
297
|
-
});
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const timeoutId = setTimeout(() => {
|
|
301
|
-
const current = this.active.get(pid);
|
|
302
|
-
if (current) {
|
|
303
|
-
removeWaiter(waiter);
|
|
304
|
-
settle({
|
|
305
|
-
pid,
|
|
306
|
-
status: "running",
|
|
307
|
-
timedOut: true,
|
|
308
|
-
process: this.toRunningSnapshot(current),
|
|
309
|
-
});
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const exited = this.exited.get(pid);
|
|
314
|
-
if (exited) {
|
|
315
|
-
removeWaiter(waiter);
|
|
316
|
-
settle({
|
|
317
|
-
pid,
|
|
318
|
-
status: "exited",
|
|
319
|
-
timedOut: false,
|
|
320
|
-
process: this.toExitedSnapshot(exited),
|
|
321
|
-
});
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
removeWaiter(waiter);
|
|
326
|
-
settle({
|
|
327
|
-
pid,
|
|
328
|
-
status: "not_found",
|
|
329
|
-
timedOut: false,
|
|
330
|
-
});
|
|
331
|
-
}, timeoutMs);
|
|
332
|
-
|
|
333
|
-
const existingWaiters = this.waiters.get(pid) ?? [];
|
|
334
|
-
existingWaiters.push(waiter);
|
|
335
|
-
this.waiters.set(pid, existingWaiters);
|
|
336
|
-
|
|
337
|
-
// Close the race window where remove() can happen between the initial
|
|
338
|
-
// active check above and waiter registration.
|
|
339
|
-
const exitedAfterRegistration = this.exited.get(pid);
|
|
340
|
-
if (exitedAfterRegistration) {
|
|
341
|
-
clearTimeout(timeoutId);
|
|
342
|
-
removeWaiter(waiter);
|
|
343
|
-
settle({
|
|
344
|
-
pid,
|
|
345
|
-
status: "exited",
|
|
346
|
-
timedOut: false,
|
|
347
|
-
process: this.toExitedSnapshot(exitedAfterRegistration),
|
|
348
|
-
});
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!this.active.has(pid)) {
|
|
353
|
-
clearTimeout(timeoutId);
|
|
354
|
-
removeWaiter(waiter);
|
|
355
|
-
settle({
|
|
356
|
-
pid,
|
|
357
|
-
status: "not_found",
|
|
358
|
-
timedOut: false,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Send SIGTERM to all tracked PIDs, then SIGKILL after a timeout
|
|
366
|
-
* for any that are still alive. Clears the registry afterwards.
|
|
367
|
-
*
|
|
368
|
-
* Returns the list of PIDs that were signalled.
|
|
369
|
-
*/
|
|
370
|
-
async killAll(): Promise<number[]> {
|
|
371
|
-
const tracked = [...this.active.keys()];
|
|
372
|
-
if (tracked.length === 0) return [];
|
|
373
|
-
|
|
374
|
-
// Phase 1: SIGTERM all tracked processes
|
|
375
|
-
const alive: number[] = [];
|
|
376
|
-
const signaledTerm = new Set<number>();
|
|
377
|
-
for (const pid of tracked) {
|
|
378
|
-
if (isTrackedProcessAlive(pid)) {
|
|
379
|
-
if (sendSignalToTrackedProcess(pid, "SIGTERM")) {
|
|
380
|
-
alive.push(pid);
|
|
381
|
-
signaledTerm.add(pid);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (alive.length === 0) {
|
|
387
|
-
for (const pid of tracked) {
|
|
388
|
-
this.remove(pid);
|
|
389
|
-
}
|
|
390
|
-
return tracked;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Phase 2: Wait, then SIGKILL any survivors
|
|
394
|
-
await new Promise<void>((resolve) => setTimeout(resolve, SIGKILL_TIMEOUT_MS));
|
|
395
|
-
|
|
396
|
-
const signaledKill = new Set<number>();
|
|
397
|
-
for (const pid of alive) {
|
|
398
|
-
if (isTrackedProcessAlive(pid)) {
|
|
399
|
-
if (sendSignalToTrackedProcess(pid, "SIGKILL")) {
|
|
400
|
-
signaledKill.add(pid);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
await waitForProcessesToExit([...signaledKill], SIGKILL_TIMEOUT_MS);
|
|
406
|
-
|
|
407
|
-
for (const pid of tracked) {
|
|
408
|
-
this.remove(pid, {
|
|
409
|
-
signal: signaledKill.has(pid) ? "SIGKILL" : signaledTerm.has(pid) ? "SIGTERM" : null,
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return tracked;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private toRunningSnapshot(record: ActiveProcessRecord, now = Date.now()): ProcessInfoSnapshot {
|
|
417
|
-
return {
|
|
418
|
-
pid: record.pid,
|
|
419
|
-
command: record.command,
|
|
420
|
-
args: [...record.args],
|
|
421
|
-
cwd: record.cwd,
|
|
422
|
-
interactive: record.interactive,
|
|
423
|
-
startedAt: record.startedAt,
|
|
424
|
-
runtimeMs: Math.max(0, now - record.startedAtMs),
|
|
425
|
-
status: "running",
|
|
426
|
-
exitCode: null,
|
|
427
|
-
signal: null,
|
|
428
|
-
endedAt: null,
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
private toExitedSnapshot(record: ExitedProcessRecord): ProcessInfoSnapshot {
|
|
433
|
-
return {
|
|
434
|
-
pid: record.pid,
|
|
435
|
-
command: record.command,
|
|
436
|
-
args: [...record.args],
|
|
437
|
-
cwd: record.cwd,
|
|
438
|
-
interactive: record.interactive,
|
|
439
|
-
startedAt: record.startedAt,
|
|
440
|
-
runtimeMs: Math.max(0, record.endedAtMs - record.startedAtMs),
|
|
441
|
-
status: "exited",
|
|
442
|
-
exitCode: record.exitCode,
|
|
443
|
-
signal: record.signal,
|
|
444
|
-
endedAt: record.endedAt,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
private pruneExitedHistory(): void {
|
|
449
|
-
while (this.exited.size > EXIT_HISTORY_LIMIT) {
|
|
450
|
-
const oldestPid = this.exited.keys().next().value as number | undefined;
|
|
451
|
-
if (oldestPid === undefined) {
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
this.exited.delete(oldestPid);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
private ensureReaper(pid: number): void {
|
|
459
|
-
if (!this.active.get(pid)?.reapOnExit) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
if (this.reapers.has(pid)) {
|
|
463
|
-
return;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const timer = setInterval(() => {
|
|
467
|
-
this.reapIfExited(pid);
|
|
468
|
-
}, PROCESS_REAP_POLL_INTERVAL_MS);
|
|
469
|
-
timer.unref?.();
|
|
470
|
-
this.reapers.set(pid, timer);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
private clearReaper(pid: number): void {
|
|
474
|
-
const timer = this.reapers.get(pid);
|
|
475
|
-
if (!timer) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
clearInterval(timer);
|
|
479
|
-
this.reapers.delete(pid);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
private reapIfExited(pid: number): boolean {
|
|
483
|
-
const record = this.active.get(pid);
|
|
484
|
-
if (!record) {
|
|
485
|
-
this.clearReaper(pid);
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (!record.reapOnExit) {
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (isTrackedProcessAlive(pid)) {
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
this.remove(pid);
|
|
498
|
-
return true;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Check whether a process is still alive by sending signal 0.
|
|
504
|
-
*/
|
|
505
|
-
function isProcessAlive(pid: number): boolean {
|
|
506
|
-
try {
|
|
507
|
-
process.kill(pid, 0);
|
|
508
|
-
return true;
|
|
509
|
-
} catch {
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function isTrackedProcessAlive(pid: number): boolean {
|
|
515
|
-
if (pid > 0) {
|
|
516
|
-
try {
|
|
517
|
-
process.kill(-pid, 0);
|
|
518
|
-
return true;
|
|
519
|
-
} catch {
|
|
520
|
-
// Fall back to the tracked pid itself when the process group is gone or unsupported.
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return isProcessAlive(pid);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function sendSignalToTrackedProcess(pid: number, signal: NodeJS.Signals): boolean {
|
|
528
|
-
try {
|
|
529
|
-
if (pid > 0) {
|
|
530
|
-
process.kill(-pid, signal);
|
|
531
|
-
return true;
|
|
532
|
-
}
|
|
533
|
-
} catch {
|
|
534
|
-
// Fall back to the tracked pid itself when there is no process group to target.
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
try {
|
|
538
|
-
process.kill(pid, signal);
|
|
539
|
-
return true;
|
|
540
|
-
} catch {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
async function waitForProcessesToExit(pids: number[], timeoutMs: number): Promise<void> {
|
|
546
|
-
if (pids.length === 0) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
const deadline = Date.now() + timeoutMs;
|
|
550
|
-
while (Date.now() < deadline) {
|
|
551
|
-
if (pids.every((pid) => !isTrackedProcessAlive(pid))) {
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
await new Promise<void>((resolve) => setTimeout(resolve, POST_KILL_POLL_INTERVAL_MS));
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function normalizeExitCode(value: number | null | undefined): number | null {
|
|
559
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
560
|
-
return null;
|
|
561
|
-
}
|
|
562
|
-
return value;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
function normalizeSignal(value: string | null | undefined): string | null {
|
|
566
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
567
|
-
return null;
|
|
568
|
-
}
|
|
569
|
-
return value;
|
|
570
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PTYSessionStore — manages PTY sessions for interactive process I/O.
|
|
3
|
-
* Separate from SpawnedProcessRegistry (which is a PID-only cleanup safety net).
|
|
4
|
-
*/
|
|
5
|
-
import type { PTYSession } from "./pty.js";
|
|
6
|
-
|
|
7
|
-
export class PTYSessionStore {
|
|
8
|
-
private sessions = new Map<number, PTYSession>();
|
|
9
|
-
|
|
10
|
-
add(pid: number, session: PTYSession): void {
|
|
11
|
-
this.sessions.set(pid, session);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
get(pid: number): PTYSession | undefined {
|
|
15
|
-
return this.sessions.get(pid);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
has(pid: number): boolean {
|
|
19
|
-
return this.sessions.has(pid);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
remove(pid: number): void {
|
|
23
|
-
this.sessions.delete(pid);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async closeAll(): Promise<void> {
|
|
27
|
-
const sessions = Array.from(this.sessions.values());
|
|
28
|
-
this.sessions.clear();
|
|
29
|
-
await Promise.allSettled(
|
|
30
|
-
sessions.map((s) => {
|
|
31
|
-
try {
|
|
32
|
-
return Promise.resolve(s.close());
|
|
33
|
-
} catch {
|
|
34
|
-
return Promise.resolve();
|
|
35
|
-
}
|
|
36
|
-
}),
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
get size(): number {
|
|
41
|
-
return this.sessions.size;
|
|
42
|
-
}
|
|
43
|
-
}
|