@lesliechan721/agent-skill-eval 0.1.0-beta.7 → 0.1.0-beta.9
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/dist/bin.js +149 -39
- package/dist/bin.js.map +1 -1
- package/dist/codex-eval-app-server.d.ts +199 -0
- package/dist/codex-eval-app-server.js +1366 -0
- package/dist/codex-eval-app-server.js.map +1 -0
- package/dist/codex-eval-cases.d.ts +43 -0
- package/dist/codex-eval-cases.js +327 -0
- package/dist/codex-eval-cases.js.map +1 -0
- package/dist/codex-eval-isolation.d.ts +61 -0
- package/dist/codex-eval-isolation.js +318 -0
- package/dist/codex-eval-isolation.js.map +1 -0
- package/dist/codex-eval-process-tree.d.ts +38 -0
- package/dist/codex-eval-process-tree.js +803 -0
- package/dist/codex-eval-process-tree.js.map +1 -0
- package/dist/codex-eval-runtime-config.d.ts +43 -0
- package/dist/codex-eval-runtime-config.js +607 -0
- package/dist/codex-eval-runtime-config.js.map +1 -0
- package/dist/codex-trigger-eval.d.ts +82 -0
- package/dist/codex-trigger-eval.js +789 -0
- package/dist/codex-trigger-eval.js.map +1 -0
- package/dist/codex-workflow.d.ts +188 -0
- package/dist/codex-workflow.js +1790 -0
- package/dist/codex-workflow.js.map +1 -0
- package/dist/credentials.d.ts +5 -0
- package/dist/credentials.js +30 -0
- package/dist/credentials.js.map +1 -0
- package/dist/custom-process.d.ts +58 -0
- package/dist/custom-process.js +409 -0
- package/dist/custom-process.js.map +1 -0
- package/dist/eval-audit.d.ts +38 -0
- package/dist/eval-audit.js +71 -0
- package/dist/eval-audit.js.map +1 -0
- package/dist/eval-file.d.ts +9 -0
- package/dist/eval-file.js +15 -0
- package/dist/eval-file.js.map +1 -0
- package/dist/eval-result.d.ts +64 -0
- package/dist/eval-result.js +144 -0
- package/dist/eval-result.js.map +1 -0
- package/dist/eval-run.d.ts +23 -0
- package/dist/eval-run.js +539 -0
- package/dist/eval-run.js.map +1 -0
- package/dist/eval-spec.d.ts +57 -0
- package/dist/eval-spec.js +120 -0
- package/dist/eval-spec.js.map +1 -0
- package/dist/eval-suite-binding.d.ts +4 -0
- package/dist/eval-suite-binding.js +19 -0
- package/dist/eval-suite-binding.js.map +1 -0
- package/dist/index.d.ts +34 -9
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/dist/json-schema.js +75 -5
- package/dist/json-schema.js.map +1 -1
- package/dist/output-path.js +2 -2
- package/dist/process-tree.d.ts +19 -1
- package/dist/process-tree.js +321 -66
- package/dist/process-tree.js.map +1 -1
- package/dist/process.d.ts +0 -2
- package/dist/process.js +76 -25
- package/dist/process.js.map +1 -1
- package/dist/provider-doctor.d.ts +33 -0
- package/dist/provider-doctor.js +86 -0
- package/dist/provider-doctor.js.map +1 -0
- package/dist/providers.d.ts +82 -0
- package/dist/providers.js +630 -0
- package/dist/providers.js.map +1 -0
- package/dist/run-paths.d.ts +8 -0
- package/dist/run-paths.js +71 -0
- package/dist/run-paths.js.map +1 -0
- package/dist/runtime-skill.d.ts +6 -0
- package/dist/runtime-skill.js +87 -3
- package/dist/runtime-skill.js.map +1 -1
- package/dist/schemas/digests.json +3 -3
- package/dist/schemas/eval-audit.schema.json +64 -0
- package/dist/schemas/eval-result.schema.json +202 -0
- package/dist/schemas/eval-spec.schema.json +109 -0
- package/dist/schemas.d.ts +1 -3
- package/dist/schemas.js +0 -17
- package/dist/schemas.js.map +1 -1
- package/dist/selected-cases.d.ts +30 -0
- package/dist/selected-cases.js +388 -0
- package/dist/selected-cases.js.map +1 -0
- package/dist/types.d.ts +2 -34
- package/package.json +6 -1
- package/dist/bundle.d.ts +0 -93
- package/dist/bundle.js +0 -216
- package/dist/bundle.js.map +0 -1
- package/dist/capabilities.d.ts +0 -2
- package/dist/capabilities.js +0 -38
- package/dist/capabilities.js.map +0 -1
- package/dist/plan.d.ts +0 -79
- package/dist/plan.js +0 -285
- package/dist/plan.js.map +0 -1
- package/dist/run.d.ts +0 -2
- package/dist/run.js +0 -592
- package/dist/run.js.map +0 -1
- package/dist/schemas/aw-skill-eval-execution-plan.schema.json +0 -220
- package/dist/schemas/eval-run-bundle.schema.json +0 -408
- package/dist/schemas/eval-runtime-capabilities.schema.json +0 -85
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import { execFile, spawn } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
const PROCESS_TERMINATION_GRACE_MS = 1_000;
|
|
5
|
+
const PROCESS_KILL_CONFIRMATION_MS = 1_000;
|
|
6
|
+
const PROCESS_FROZEN_TREE_KILL_CONFIRMATION_MS = 2_000;
|
|
7
|
+
const PROCESS_TREE_POLL_MS = 250;
|
|
8
|
+
const PROCESS_TOKEN_SCAN_TIMEOUT_MS = 3_000;
|
|
9
|
+
export const CODEX_EVAL_PROCESS_TREE_ID_ENV = 'AW_CODEX_EVAL_PROCESS_TREE_ID';
|
|
10
|
+
export const PROCESS_TREE_CLEANUP_UNCONFIRMED_PREFIX = 'Could not confirm process-tree cleanup';
|
|
11
|
+
export function createDirectChildProcessMonitor(child) {
|
|
12
|
+
let terminationPromise;
|
|
13
|
+
return {
|
|
14
|
+
terminate: () => {
|
|
15
|
+
terminationPromise ??= new Promise((resolve) => {
|
|
16
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
17
|
+
resolve([]);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let closed = false;
|
|
21
|
+
let settled = false;
|
|
22
|
+
let forceTimer;
|
|
23
|
+
let settleTimer;
|
|
24
|
+
const settle = (diagnostics) => {
|
|
25
|
+
if (settled)
|
|
26
|
+
return;
|
|
27
|
+
settled = true;
|
|
28
|
+
if (forceTimer)
|
|
29
|
+
clearTimeout(forceTimer);
|
|
30
|
+
if (settleTimer)
|
|
31
|
+
clearTimeout(settleTimer);
|
|
32
|
+
child.off('close', onClose);
|
|
33
|
+
resolve(diagnostics);
|
|
34
|
+
};
|
|
35
|
+
const onClose = () => {
|
|
36
|
+
closed = true;
|
|
37
|
+
settle([]);
|
|
38
|
+
};
|
|
39
|
+
child.once('close', onClose);
|
|
40
|
+
try {
|
|
41
|
+
child.kill('SIGTERM');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// The direct child can close before SIGTERM is delivered.
|
|
45
|
+
}
|
|
46
|
+
forceTimer = setTimeout(() => {
|
|
47
|
+
if (closed || child.exitCode !== null || child.signalCode !== null)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
child.kill('SIGKILL');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// The direct child can close before SIGKILL is delivered.
|
|
54
|
+
}
|
|
55
|
+
}, 1_000);
|
|
56
|
+
settleTimer = setTimeout(() => {
|
|
57
|
+
settle(closed || child.exitCode !== null || child.signalCode !== null
|
|
58
|
+
? []
|
|
59
|
+
: ['Could not confirm direct eval child termination; descendants are not tracked.']);
|
|
60
|
+
}, 2_000);
|
|
61
|
+
});
|
|
62
|
+
return terminationPromise;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function processTreeCleanupIsUnconfirmed(diagnostics) {
|
|
67
|
+
return diagnostics.some((entry) => entry.startsWith(PROCESS_TREE_CLEANUP_UNCONFIRMED_PREFIX) ||
|
|
68
|
+
entry.startsWith('Could not confirm process-tree termination') ||
|
|
69
|
+
entry.startsWith('Could not confirm Windows process-tree termination') ||
|
|
70
|
+
entry.startsWith('Could not verify Windows process-tree termination'));
|
|
71
|
+
}
|
|
72
|
+
export function scrubProcessTreeId(value, treeId) {
|
|
73
|
+
if (typeof value === 'string') {
|
|
74
|
+
return value.replaceAll(treeId, '[process-tree-id]');
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
return value.map((entry) => scrubProcessTreeId(entry, treeId));
|
|
78
|
+
}
|
|
79
|
+
if (value && typeof value === 'object') {
|
|
80
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
81
|
+
scrubProcessTreeId(key, treeId),
|
|
82
|
+
scrubProcessTreeId(entry, treeId),
|
|
83
|
+
]));
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function delay(milliseconds) {
|
|
88
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
89
|
+
}
|
|
90
|
+
async function readPosixProcessTable(timeoutMs = PROCESS_TERMINATION_GRACE_MS) {
|
|
91
|
+
const result = await execFileAsync('/bin/ps', ['-Ao', 'pid=,ppid=,state=,lstart='], {
|
|
92
|
+
encoding: 'utf8',
|
|
93
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
94
|
+
timeout: timeoutMs,
|
|
95
|
+
});
|
|
96
|
+
const table = new Map();
|
|
97
|
+
for (const line of result.stdout.split('\n')) {
|
|
98
|
+
const [pidText, ppidText, state, ...startTimeParts] = line.trim().split(/\s+/u);
|
|
99
|
+
const pid = Number(pidText);
|
|
100
|
+
const ppid = Number(ppidText);
|
|
101
|
+
const startTime = startTimeParts.join(' ');
|
|
102
|
+
if (Number.isInteger(pid) && Number.isInteger(ppid) && state && startTime) {
|
|
103
|
+
table.set(pid, { ppid, state, startTime });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return table;
|
|
107
|
+
}
|
|
108
|
+
async function readTaggedPosixProcessTable(treeId, timeoutMs = PROCESS_TERMINATION_GRACE_MS) {
|
|
109
|
+
const marker = `${CODEX_EVAL_PROCESS_TREE_ID_ENV}=${treeId}`;
|
|
110
|
+
const table = new Map();
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const child = spawn('/bin/ps', ['eww', '-Ao', 'pid=,ppid=,state=,lstart=,command='], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
113
|
+
let linePrefix = '';
|
|
114
|
+
let markerTail = '';
|
|
115
|
+
let lineHasMarker = false;
|
|
116
|
+
let settled = false;
|
|
117
|
+
const finish = (error) => {
|
|
118
|
+
if (settled)
|
|
119
|
+
return;
|
|
120
|
+
settled = true;
|
|
121
|
+
clearTimeout(timeout);
|
|
122
|
+
if (error)
|
|
123
|
+
reject(error);
|
|
124
|
+
else
|
|
125
|
+
resolve(table);
|
|
126
|
+
};
|
|
127
|
+
const finishLine = () => {
|
|
128
|
+
if (lineHasMarker) {
|
|
129
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(\S+\s+\S+\s+\d+\s+\S+\s+\d+)/u.exec(linePrefix);
|
|
130
|
+
if (match) {
|
|
131
|
+
const pid = Number(match[1]);
|
|
132
|
+
const ppid = Number(match[2]);
|
|
133
|
+
if (Number.isInteger(pid) && Number.isInteger(ppid)) {
|
|
134
|
+
table.set(pid, {
|
|
135
|
+
ppid,
|
|
136
|
+
state: match[3],
|
|
137
|
+
startTime: match[4].split(/\s+/u).join(' '),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
linePrefix = '';
|
|
143
|
+
markerTail = '';
|
|
144
|
+
lineHasMarker = false;
|
|
145
|
+
};
|
|
146
|
+
child.stdout.setEncoding('utf8');
|
|
147
|
+
child.stdout.on('data', (chunk) => {
|
|
148
|
+
for (const segment of chunk.split(/(\n)/u)) {
|
|
149
|
+
if (segment === '\n') {
|
|
150
|
+
finishLine();
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (linePrefix.length < 256) {
|
|
154
|
+
linePrefix += segment.slice(0, 256 - linePrefix.length);
|
|
155
|
+
}
|
|
156
|
+
const markerCandidate = markerTail + segment;
|
|
157
|
+
if (markerCandidate.includes(marker))
|
|
158
|
+
lineHasMarker = true;
|
|
159
|
+
markerTail = markerCandidate.slice(-(marker.length - 1));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
child.once('error', finish);
|
|
163
|
+
child.once('close', (exitCode, signal) => {
|
|
164
|
+
finishLine();
|
|
165
|
+
if (exitCode === 0)
|
|
166
|
+
finish();
|
|
167
|
+
else
|
|
168
|
+
finish(new Error(`POSIX process-tree token scan failed: exit=${String(exitCode)}, signal=${String(signal)}.`));
|
|
169
|
+
});
|
|
170
|
+
const timeout = setTimeout(() => {
|
|
171
|
+
try {
|
|
172
|
+
child.kill('SIGKILL');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// The ps subprocess can exit before the timeout fallback.
|
|
176
|
+
}
|
|
177
|
+
finish(new Error(`POSIX process-tree token scan exceeded ${timeoutMs}ms.`));
|
|
178
|
+
}, timeoutMs);
|
|
179
|
+
timeout.unref();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
function readTaggedPosixProcessTableWithTimeout(treeId, readTaggedProcessTable, timeoutMs) {
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
let settled = false;
|
|
185
|
+
const finish = (result) => {
|
|
186
|
+
if (settled)
|
|
187
|
+
return;
|
|
188
|
+
settled = true;
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
if ('table' in result)
|
|
191
|
+
resolve(result.table);
|
|
192
|
+
else
|
|
193
|
+
reject(result.error);
|
|
194
|
+
};
|
|
195
|
+
const timer = setTimeout(() => finish({
|
|
196
|
+
error: new Error(`POSIX process-tree token scan exceeded ${timeoutMs}ms.`),
|
|
197
|
+
}), timeoutMs);
|
|
198
|
+
timer.unref();
|
|
199
|
+
Promise.resolve().then(() => readTaggedProcessTable(treeId, timeoutMs)).then((table) => finish({ table }), (error) => finish({ error }));
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function readPosixProcessTableWithTimeout(readProcessTable, timeoutMs, stage) {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
let settled = false;
|
|
205
|
+
const finish = (result) => {
|
|
206
|
+
if (settled)
|
|
207
|
+
return;
|
|
208
|
+
settled = true;
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
if ('table' in result)
|
|
211
|
+
resolve(result.table);
|
|
212
|
+
else
|
|
213
|
+
reject(result.error);
|
|
214
|
+
};
|
|
215
|
+
const timer = setTimeout(() => finish({
|
|
216
|
+
error: new Error(`POSIX process-tree ${stage} scan exceeded ${timeoutMs}ms.`),
|
|
217
|
+
}), timeoutMs);
|
|
218
|
+
timer.unref();
|
|
219
|
+
Promise.resolve().then(() => readProcessTable(timeoutMs)).then((table) => finish({ table }), (error) => finish({ error }));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function recordKnownDescendants(root, knownDescendants, table) {
|
|
223
|
+
const childrenByParent = new Map();
|
|
224
|
+
for (const [pid, entry] of table) {
|
|
225
|
+
const children = childrenByParent.get(entry.ppid) ?? [];
|
|
226
|
+
children.push(pid);
|
|
227
|
+
childrenByParent.set(entry.ppid, children);
|
|
228
|
+
}
|
|
229
|
+
const roots = [root, ...knownDescendants.values()].filter((identity) => table.get(identity.pid)?.startTime === identity.startTime);
|
|
230
|
+
const queue = roots.map((identity) => identity.pid);
|
|
231
|
+
const visited = new Set(queue);
|
|
232
|
+
while (queue.length > 0) {
|
|
233
|
+
const parent = queue.shift();
|
|
234
|
+
for (const childPid of childrenByParent.get(parent) ?? []) {
|
|
235
|
+
if (visited.has(childPid))
|
|
236
|
+
continue;
|
|
237
|
+
visited.add(childPid);
|
|
238
|
+
const entry = table.get(childPid);
|
|
239
|
+
const knownIdentity = knownDescendants.get(childPid);
|
|
240
|
+
if (knownIdentity && knownIdentity.startTime !== entry.startTime)
|
|
241
|
+
continue;
|
|
242
|
+
knownDescendants.set(childPid, {
|
|
243
|
+
pid: childPid,
|
|
244
|
+
startTime: entry.startTime,
|
|
245
|
+
});
|
|
246
|
+
queue.push(childPid);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function processIsRunning(table, identity) {
|
|
251
|
+
const entry = table.get(identity.pid);
|
|
252
|
+
return entry !== undefined &&
|
|
253
|
+
entry.startTime === identity.startTime &&
|
|
254
|
+
!entry.state.startsWith('Z');
|
|
255
|
+
}
|
|
256
|
+
function signalPosixPid(pid, signal, kill = process.kill.bind(process)) {
|
|
257
|
+
try {
|
|
258
|
+
kill(pid, signal);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error.code !== 'ESRCH')
|
|
263
|
+
throw error;
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function signalKnownPosixTree(root, knownDescendants, table, signal, kill = process.kill.bind(process)) {
|
|
268
|
+
for (const identity of [...knownDescendants.values()].reverse()) {
|
|
269
|
+
if (processIsRunning(table, identity))
|
|
270
|
+
signalPosixPid(identity.pid, signal, kill);
|
|
271
|
+
}
|
|
272
|
+
if (processIsRunning(table, root))
|
|
273
|
+
signalPosixPid(root.pid, signal, kill);
|
|
274
|
+
}
|
|
275
|
+
function signalPosixProcessGroup(rootPid, signal, kill = process.kill.bind(process)) {
|
|
276
|
+
try {
|
|
277
|
+
kill(-rootPid, signal);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
const code = error.code;
|
|
281
|
+
if (code !== 'ESRCH' && code !== 'EPERM')
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function posixProcessGroupIsRunning(rootPid, kill = process.kill.bind(process)) {
|
|
286
|
+
try {
|
|
287
|
+
kill(-rootPid, 0);
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const code = error.code;
|
|
292
|
+
return code !== 'ESRCH' && code !== 'EPERM';
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function knownPosixTreeIsFrozen(root, knownDescendants, table) {
|
|
296
|
+
return [root, ...knownDescendants.values()].every((identity) => {
|
|
297
|
+
const entry = table.get(identity.pid);
|
|
298
|
+
return entry === undefined ||
|
|
299
|
+
entry.startTime !== identity.startTime ||
|
|
300
|
+
entry.state.startsWith('T') ||
|
|
301
|
+
entry.state.startsWith('Z');
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
function knownPosixTreeIsRunning(root, knownDescendants, table) {
|
|
305
|
+
return [root, ...knownDescendants.values()].some((identity) => processIsRunning(table, identity));
|
|
306
|
+
}
|
|
307
|
+
async function terminatePosixProcessGroupFallback(rootPid, kill, allowed = true, deadline = Date.now() + PROCESS_KILL_CONFIRMATION_MS) {
|
|
308
|
+
if (!allowed)
|
|
309
|
+
return false;
|
|
310
|
+
while (Date.now() < deadline) {
|
|
311
|
+
signalPosixProcessGroup(rootPid, 'SIGKILL', kill);
|
|
312
|
+
if (!posixProcessGroupIsRunning(rootPid, kill))
|
|
313
|
+
return true;
|
|
314
|
+
const remainingMs = deadline - Date.now();
|
|
315
|
+
if (remainingMs > 0) {
|
|
316
|
+
await delay(Math.min(PROCESS_TREE_POLL_MS, remainingMs));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
async function terminateKnownPosixProcessTree(rootPid, dependencies = {}, observedRoot, observedDescendants = new Map(), observedTable, preFrozen = false) {
|
|
322
|
+
const readProcessTable = dependencies.readProcessTable ?? readPosixProcessTable;
|
|
323
|
+
const kill = dependencies.kill ?? process.kill.bind(process);
|
|
324
|
+
const diagnostics = [];
|
|
325
|
+
const knownDescendants = new Map(observedDescendants);
|
|
326
|
+
const canUseProcessGroup = () => dependencies.canUseProcessGroup?.() ?? true;
|
|
327
|
+
const allowRootIdentityDiscovery = dependencies.allowRootIdentityDiscovery ?? true;
|
|
328
|
+
let table;
|
|
329
|
+
let root;
|
|
330
|
+
if (preFrozen &&
|
|
331
|
+
observedTable &&
|
|
332
|
+
(observedRoot || knownDescendants.size > 0)) {
|
|
333
|
+
root = observedRoot ?? { pid: rootPid, startTime: '<unobserved-root>' };
|
|
334
|
+
table = observedTable;
|
|
335
|
+
const killDeadline = Date.now() + PROCESS_FROZEN_TREE_KILL_CONFIRMATION_MS;
|
|
336
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGKILL', kill);
|
|
337
|
+
while (Date.now() < killDeadline) {
|
|
338
|
+
try {
|
|
339
|
+
const remainingMs = killDeadline - Date.now();
|
|
340
|
+
if (remainingMs <= 0)
|
|
341
|
+
break;
|
|
342
|
+
table = await readPosixProcessTableWithTimeout(readProcessTable, remainingMs, 'kill-confirmation');
|
|
343
|
+
recordKnownDescendants(root, knownDescendants, table);
|
|
344
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGKILL', kill);
|
|
345
|
+
if (!knownPosixTreeIsRunning(root, knownDescendants, table)) {
|
|
346
|
+
return diagnostics;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
diagnostics.push(`Could not verify the pre-frozen POSIX process tree after SIGKILL: ${error instanceof Error ? error.message : String(error)}`);
|
|
351
|
+
if (await terminatePosixProcessGroupFallback(rootPid, kill, canUseProcessGroup(), killDeadline))
|
|
352
|
+
return diagnostics;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
const remainingMs = killDeadline - Date.now();
|
|
356
|
+
if (remainingMs > 0) {
|
|
357
|
+
await delay(Math.min(PROCESS_TREE_POLL_MS, remainingMs));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
diagnostics.push(`Could not confirm process-tree termination within ` +
|
|
361
|
+
`${PROCESS_FROZEN_TREE_KILL_CONFIRMATION_MS}ms after SIGKILL; process ${rootPid} ` +
|
|
362
|
+
'or one of its descendants may still be running.');
|
|
363
|
+
return diagnostics;
|
|
364
|
+
}
|
|
365
|
+
const freezeDeadline = Date.now() + PROCESS_TERMINATION_GRACE_MS;
|
|
366
|
+
try {
|
|
367
|
+
const initialRemainingMs = freezeDeadline - Date.now();
|
|
368
|
+
if (initialRemainingMs <= 0) {
|
|
369
|
+
throw new Error('POSIX process-tree initial scan deadline was exhausted.');
|
|
370
|
+
}
|
|
371
|
+
table = await readPosixProcessTableWithTimeout(readProcessTable, initialRemainingMs, 'initial');
|
|
372
|
+
const rootEntry = table.get(rootPid);
|
|
373
|
+
if (observedRoot) {
|
|
374
|
+
root = observedRoot;
|
|
375
|
+
recordKnownDescendants(root, knownDescendants, table);
|
|
376
|
+
}
|
|
377
|
+
else if (!allowRootIdentityDiscovery ||
|
|
378
|
+
!rootEntry ||
|
|
379
|
+
rootEntry.state.startsWith('Z')) {
|
|
380
|
+
if (knownDescendants.size > 0) {
|
|
381
|
+
root = { pid: rootPid, startTime: '<unobserved-root>' };
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
const killDeadline = Date.now() + PROCESS_KILL_CONFIRMATION_MS;
|
|
385
|
+
if (await terminatePosixProcessGroupFallback(rootPid, kill, canUseProcessGroup(), killDeadline))
|
|
386
|
+
return diagnostics;
|
|
387
|
+
diagnostics.push(`${PROCESS_TREE_CLEANUP_UNCONFIRMED_PREFIX}: root identity and process-group ` +
|
|
388
|
+
`ownership for ${rootPid} are unavailable.`);
|
|
389
|
+
return diagnostics;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
root = { pid: rootPid, startTime: rootEntry.startTime };
|
|
394
|
+
}
|
|
395
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGSTOP', kill);
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
diagnostics.push(`Could not inspect and freeze the POSIX process tree: ${error instanceof Error ? error.message : String(error)}`);
|
|
399
|
+
const killDeadline = Date.now() + PROCESS_KILL_CONFIRMATION_MS;
|
|
400
|
+
if (observedTable && (observedRoot || knownDescendants.size > 0)) {
|
|
401
|
+
const fallbackRoot = observedRoot ?? {
|
|
402
|
+
pid: rootPid,
|
|
403
|
+
startTime: '<unobserved-root>',
|
|
404
|
+
};
|
|
405
|
+
signalKnownPosixTree(fallbackRoot, knownDescendants, observedTable, 'SIGKILL', kill);
|
|
406
|
+
}
|
|
407
|
+
if (!(await terminatePosixProcessGroupFallback(rootPid, kill, canUseProcessGroup(), killDeadline))) {
|
|
408
|
+
diagnostics.push(`${PROCESS_TREE_CLEANUP_UNCONFIRMED_PREFIX}: no fresh POSIX snapshot or trusted ` +
|
|
409
|
+
`process-group ownership is available for ${rootPid}.`);
|
|
410
|
+
}
|
|
411
|
+
return diagnostics;
|
|
412
|
+
}
|
|
413
|
+
let useProcessGroupFallback = false;
|
|
414
|
+
while (Date.now() < freezeDeadline) {
|
|
415
|
+
try {
|
|
416
|
+
const remainingMs = freezeDeadline - Date.now();
|
|
417
|
+
if (remainingMs <= 0)
|
|
418
|
+
break;
|
|
419
|
+
table = await readPosixProcessTableWithTimeout(readProcessTable, remainingMs, 'lineage');
|
|
420
|
+
recordKnownDescendants(root, knownDescendants, table);
|
|
421
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGSTOP', kill);
|
|
422
|
+
if (knownPosixTreeIsFrozen(root, knownDescendants, table))
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
diagnostics.push(`Could not refresh the frozen POSIX process tree; used process-group fallback: ${error instanceof Error ? error.message : String(error)}`);
|
|
427
|
+
useProcessGroupFallback = true;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
const remainingMs = freezeDeadline - Date.now();
|
|
431
|
+
if (remainingMs > 0) {
|
|
432
|
+
await delay(Math.min(PROCESS_TREE_POLL_MS, remainingMs));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!useProcessGroupFallback && !knownPosixTreeIsFrozen(root, knownDescendants, table)) {
|
|
436
|
+
diagnostics.push(`Could not confirm process-tree freeze within ${PROCESS_TERMINATION_GRACE_MS}ms; ` +
|
|
437
|
+
'continued with SIGKILL.');
|
|
438
|
+
}
|
|
439
|
+
const killDeadline = Date.now() + PROCESS_KILL_CONFIRMATION_MS;
|
|
440
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGKILL', kill);
|
|
441
|
+
if (useProcessGroupFallback) {
|
|
442
|
+
if (await terminatePosixProcessGroupFallback(rootPid, kill, canUseProcessGroup(), killDeadline))
|
|
443
|
+
return diagnostics;
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
while (Date.now() < killDeadline) {
|
|
447
|
+
try {
|
|
448
|
+
const remainingMs = killDeadline - Date.now();
|
|
449
|
+
if (remainingMs <= 0)
|
|
450
|
+
break;
|
|
451
|
+
table = await readPosixProcessTableWithTimeout(readProcessTable, remainingMs, 'kill-confirmation');
|
|
452
|
+
recordKnownDescendants(root, knownDescendants, table);
|
|
453
|
+
signalKnownPosixTree(root, knownDescendants, table, 'SIGKILL', kill);
|
|
454
|
+
if (!knownPosixTreeIsRunning(root, knownDescendants, table))
|
|
455
|
+
return diagnostics;
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
diagnostics.push(`Could not verify the frozen POSIX process tree after SIGKILL; ` +
|
|
459
|
+
`used process-group fallback: ${error instanceof Error ? error.message : String(error)}`);
|
|
460
|
+
useProcessGroupFallback = true;
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
const remainingMs = killDeadline - Date.now();
|
|
464
|
+
if (remainingMs > 0) {
|
|
465
|
+
await delay(Math.min(PROCESS_TREE_POLL_MS, remainingMs));
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!useProcessGroupFallback) {
|
|
469
|
+
diagnostics.push(`Could not confirm process-tree termination within ${PROCESS_KILL_CONFIRMATION_MS}ms ` +
|
|
470
|
+
`after SIGKILL; process ${rootPid} or one of its descendants may still be running.`);
|
|
471
|
+
return diagnostics;
|
|
472
|
+
}
|
|
473
|
+
if (await terminatePosixProcessGroupFallback(rootPid, kill, canUseProcessGroup(), killDeadline))
|
|
474
|
+
return diagnostics;
|
|
475
|
+
}
|
|
476
|
+
diagnostics.push(`Could not confirm process-tree termination within ${PROCESS_KILL_CONFIRMATION_MS}ms after SIGKILL; ` +
|
|
477
|
+
`process group ${rootPid} may still be running.`);
|
|
478
|
+
return diagnostics;
|
|
479
|
+
}
|
|
480
|
+
export async function terminatePosixProcessTree(rootPid, dependencies = {}) {
|
|
481
|
+
return terminateKnownPosixProcessTree(rootPid, dependencies);
|
|
482
|
+
}
|
|
483
|
+
export function createPosixProcessTreeMonitor(rootPid, dependencies = {}) {
|
|
484
|
+
const readProcessTable = dependencies.readProcessTable ?? readPosixProcessTable;
|
|
485
|
+
const readTaggedProcessTable = dependencies.readTaggedProcessTable ??
|
|
486
|
+
readTaggedPosixProcessTable;
|
|
487
|
+
const kill = dependencies.kill ?? process.kill.bind(process);
|
|
488
|
+
let observedRoot;
|
|
489
|
+
const observedDescendants = new Map();
|
|
490
|
+
const diagnostics = [];
|
|
491
|
+
let stopped = false;
|
|
492
|
+
let pollTimer;
|
|
493
|
+
let pendingPoll;
|
|
494
|
+
let latestValidatedTable;
|
|
495
|
+
let allowLineageRootBinding = false;
|
|
496
|
+
const observeTable = (table) => {
|
|
497
|
+
latestValidatedTable = table;
|
|
498
|
+
const rootEntry = table.get(rootPid);
|
|
499
|
+
if (allowLineageRootBinding &&
|
|
500
|
+
!observedRoot &&
|
|
501
|
+
rootEntry &&
|
|
502
|
+
!rootEntry.state.startsWith('Z')) {
|
|
503
|
+
observedRoot = { pid: rootPid, startTime: rootEntry.startTime };
|
|
504
|
+
}
|
|
505
|
+
if (observedRoot) {
|
|
506
|
+
recordKnownDescendants(observedRoot, observedDescendants, table);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
const observeTaggedTable = (taggedTable, freshTable) => {
|
|
510
|
+
const taggedRoot = taggedTable.get(rootPid);
|
|
511
|
+
if (!observedRoot &&
|
|
512
|
+
taggedRoot &&
|
|
513
|
+
freshTable.get(rootPid)?.startTime === taggedRoot.startTime) {
|
|
514
|
+
observedRoot = { pid: rootPid, startTime: taggedRoot.startTime };
|
|
515
|
+
}
|
|
516
|
+
for (const [pid, entry] of taggedTable) {
|
|
517
|
+
if (pid === rootPid)
|
|
518
|
+
continue;
|
|
519
|
+
if (freshTable.get(pid)?.startTime !== entry.startTime)
|
|
520
|
+
continue;
|
|
521
|
+
const knownIdentity = observedDescendants.get(pid);
|
|
522
|
+
if (knownIdentity && knownIdentity.startTime !== entry.startTime)
|
|
523
|
+
continue;
|
|
524
|
+
observedDescendants.set(pid, { pid, startTime: entry.startTime });
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const signalObservedDescendants = (table, signal) => {
|
|
528
|
+
for (const identity of [...observedDescendants.values()].reverse()) {
|
|
529
|
+
if (table.get(identity.pid)?.startTime !== identity.startTime)
|
|
530
|
+
continue;
|
|
531
|
+
signalPosixPid(identity.pid, signal, kill);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const observedDescendantsAreFrozen = (table) => [...observedDescendants.values()].every((identity) => {
|
|
535
|
+
const entry = table.get(identity.pid);
|
|
536
|
+
return entry === undefined ||
|
|
537
|
+
entry.startTime !== identity.startTime ||
|
|
538
|
+
entry.state.startsWith('T') ||
|
|
539
|
+
entry.state.startsWith('Z');
|
|
540
|
+
});
|
|
541
|
+
const observedTreeIsFrozen = (table) => {
|
|
542
|
+
const rootFrozen = !observedRoot || knownPosixTreeIsFrozen(observedRoot, new Map(), table);
|
|
543
|
+
return rootFrozen && observedDescendantsAreFrozen(table);
|
|
544
|
+
};
|
|
545
|
+
const recordObservedLineage = (table) => {
|
|
546
|
+
if (observedRoot) {
|
|
547
|
+
recordKnownDescendants(observedRoot, observedDescendants, table);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
for (const identity of [...observedDescendants.values()]) {
|
|
551
|
+
recordKnownDescendants(identity, observedDescendants, table);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
const stopObservedTree = (table) => {
|
|
555
|
+
if (observedRoot &&
|
|
556
|
+
table.get(observedRoot.pid)?.startTime === observedRoot.startTime) {
|
|
557
|
+
signalPosixPid(observedRoot.pid, 'SIGSTOP', kill);
|
|
558
|
+
}
|
|
559
|
+
signalObservedDescendants(table, 'SIGSTOP');
|
|
560
|
+
};
|
|
561
|
+
const recordMonitorError = (error) => {
|
|
562
|
+
if (diagnostics.length === 0) {
|
|
563
|
+
diagnostics.push(`Could not monitor the POSIX process tree before cleanup: ${error instanceof Error ? error.message : String(error)}`);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const poll = () => {
|
|
567
|
+
pendingPoll = readPosixProcessTableWithTimeout(readProcessTable, PROCESS_TERMINATION_GRACE_MS, 'lineage')
|
|
568
|
+
.then(observeTable, recordMonitorError).finally(() => {
|
|
569
|
+
pendingPoll = undefined;
|
|
570
|
+
if (!stopped) {
|
|
571
|
+
pollTimer = setTimeout(poll, PROCESS_TREE_POLL_MS);
|
|
572
|
+
pollTimer.unref();
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
};
|
|
576
|
+
let initialRootStopped = false;
|
|
577
|
+
try {
|
|
578
|
+
initialRootStopped = signalPosixPid(rootPid, 'SIGSTOP', kill);
|
|
579
|
+
allowLineageRootBinding = initialRootStopped;
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
recordMonitorError(error);
|
|
583
|
+
}
|
|
584
|
+
pendingPoll = readPosixProcessTableWithTimeout(readProcessTable, PROCESS_TERMINATION_GRACE_MS, 'initial')
|
|
585
|
+
.then(observeTable, recordMonitorError).finally(() => {
|
|
586
|
+
allowLineageRootBinding = false;
|
|
587
|
+
try {
|
|
588
|
+
if (initialRootStopped)
|
|
589
|
+
signalPosixPid(rootPid, 'SIGCONT', kill);
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
recordMonitorError(error);
|
|
593
|
+
}
|
|
594
|
+
pendingPoll = undefined;
|
|
595
|
+
if (!stopped) {
|
|
596
|
+
pollTimer = setTimeout(poll, PROCESS_TREE_POLL_MS);
|
|
597
|
+
pollTimer.unref();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
return {
|
|
601
|
+
terminate: async () => {
|
|
602
|
+
if (!stopped) {
|
|
603
|
+
stopped = true;
|
|
604
|
+
if (pollTimer)
|
|
605
|
+
clearTimeout(pollTimer);
|
|
606
|
+
await pendingPoll;
|
|
607
|
+
}
|
|
608
|
+
let tokenCleanupConfirmed = false;
|
|
609
|
+
if (dependencies.treeId) {
|
|
610
|
+
const tokenScanDeadline = Date.now() + PROCESS_TOKEN_SCAN_TIMEOUT_MS;
|
|
611
|
+
while (Date.now() < tokenScanDeadline) {
|
|
612
|
+
try {
|
|
613
|
+
const preFreezeRemainingMs = tokenScanDeadline - Date.now();
|
|
614
|
+
if (preFreezeRemainingMs <= 0)
|
|
615
|
+
break;
|
|
616
|
+
const preFreezeTable = await readPosixProcessTableWithTimeout(readProcessTable, preFreezeRemainingMs, 'lineage');
|
|
617
|
+
latestValidatedTable = preFreezeTable;
|
|
618
|
+
recordObservedLineage(preFreezeTable);
|
|
619
|
+
stopObservedTree(preFreezeTable);
|
|
620
|
+
const freezeConfirmationRemainingMs = tokenScanDeadline - Date.now();
|
|
621
|
+
if (freezeConfirmationRemainingMs <= 0)
|
|
622
|
+
break;
|
|
623
|
+
const freezeConfirmationTable = await readPosixProcessTableWithTimeout(readProcessTable, freezeConfirmationRemainingMs, 'confirmation');
|
|
624
|
+
latestValidatedTable = freezeConfirmationTable;
|
|
625
|
+
recordObservedLineage(freezeConfirmationTable);
|
|
626
|
+
stopObservedTree(freezeConfirmationTable);
|
|
627
|
+
if (!observedTreeIsFrozen(freezeConfirmationTable)) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const remainingMs = tokenScanDeadline - Date.now();
|
|
631
|
+
if (remainingMs <= 0)
|
|
632
|
+
break;
|
|
633
|
+
const taggedTable = await readTaggedPosixProcessTableWithTimeout(dependencies.treeId, readTaggedProcessTable, Math.min(PROCESS_TERMINATION_GRACE_MS, remainingMs));
|
|
634
|
+
const tagContainsOnlyFrozenKnownIdentities = [...taggedTable].every(([pid, entry]) => {
|
|
635
|
+
const identity = pid === rootPid
|
|
636
|
+
? observedRoot
|
|
637
|
+
: observedDescendants.get(pid);
|
|
638
|
+
const frozenEntry = freezeConfirmationTable.get(pid);
|
|
639
|
+
return identity?.startTime === entry.startTime &&
|
|
640
|
+
frozenEntry?.startTime === entry.startTime &&
|
|
641
|
+
(frozenEntry.state.startsWith('T') || frozenEntry.state.startsWith('Z'));
|
|
642
|
+
});
|
|
643
|
+
if (tagContainsOnlyFrozenKnownIdentities) {
|
|
644
|
+
tokenCleanupConfirmed = true;
|
|
645
|
+
latestValidatedTable = freezeConfirmationTable;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
const lineageRemainingMs = tokenScanDeadline - Date.now();
|
|
649
|
+
if (lineageRemainingMs <= 0)
|
|
650
|
+
break;
|
|
651
|
+
const lineageTable = await readPosixProcessTableWithTimeout(readProcessTable, lineageRemainingMs, 'lineage');
|
|
652
|
+
latestValidatedTable = lineageTable;
|
|
653
|
+
observeTaggedTable(taggedTable, lineageTable);
|
|
654
|
+
recordObservedLineage(lineageTable);
|
|
655
|
+
stopObservedTree(lineageTable);
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
recordMonitorError(error);
|
|
659
|
+
const remainingMs = tokenScanDeadline - Date.now();
|
|
660
|
+
if (remainingMs > 0) {
|
|
661
|
+
await delay(Math.min(PROCESS_TREE_POLL_MS, remainingMs));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (!tokenCleanupConfirmed) {
|
|
666
|
+
diagnostics.push(`${PROCESS_TREE_CLEANUP_UNCONFIRMED_PREFIX}: the POSIX token scan did not ` +
|
|
667
|
+
`reach a frozen fixed point within ${PROCESS_TOKEN_SCAN_TIMEOUT_MS}ms.`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return [
|
|
671
|
+
...diagnostics,
|
|
672
|
+
...(await terminateKnownPosixProcessTree(rootPid, { ...dependencies, allowRootIdentityDiscovery: false }, observedRoot, observedDescendants, latestValidatedTable, tokenCleanupConfirmed)),
|
|
673
|
+
];
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function observeChildClose(child) {
|
|
678
|
+
let closed = Boolean((child.exitCode !== null || child.signalCode !== null) &&
|
|
679
|
+
child.stdout?.destroyed &&
|
|
680
|
+
child.stderr?.destroyed);
|
|
681
|
+
let resolveClose;
|
|
682
|
+
const closePromise = new Promise((resolve) => {
|
|
683
|
+
resolveClose = resolve;
|
|
684
|
+
});
|
|
685
|
+
const onClose = () => {
|
|
686
|
+
closed = true;
|
|
687
|
+
resolveClose?.();
|
|
688
|
+
};
|
|
689
|
+
child.once('close', onClose);
|
|
690
|
+
return {
|
|
691
|
+
wait: async (timeoutMs) => {
|
|
692
|
+
if (closed)
|
|
693
|
+
return true;
|
|
694
|
+
let timer;
|
|
695
|
+
const timedOut = new Promise((resolve) => {
|
|
696
|
+
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
697
|
+
});
|
|
698
|
+
const result = await Promise.race([closePromise.then(() => true), timedOut]);
|
|
699
|
+
if (timer)
|
|
700
|
+
clearTimeout(timer);
|
|
701
|
+
return result;
|
|
702
|
+
},
|
|
703
|
+
dispose: () => child.off('close', onClose),
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
export function releaseChildProcessHandles(child) {
|
|
707
|
+
child.stdin?.destroy();
|
|
708
|
+
child.stdout?.destroy();
|
|
709
|
+
child.stderr?.destroy();
|
|
710
|
+
child.unref();
|
|
711
|
+
}
|
|
712
|
+
export async function terminateWindowsProcessTree(child, options = {}) {
|
|
713
|
+
if (child.pid === undefined)
|
|
714
|
+
return [];
|
|
715
|
+
const closeTimeoutMs = options.closeTimeoutMs ?? PROCESS_KILL_CONFIRMATION_MS;
|
|
716
|
+
const closeObserver = observeChildClose(child);
|
|
717
|
+
const runTaskkill = options.runTaskkill ?? (async (pid) => {
|
|
718
|
+
await execFileAsync('taskkill.exe', ['/pid', String(pid), '/t', '/f'], {
|
|
719
|
+
encoding: 'utf8',
|
|
720
|
+
timeout: PROCESS_TERMINATION_GRACE_MS * 5,
|
|
721
|
+
windowsHide: true,
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
try {
|
|
725
|
+
await runTaskkill(child.pid);
|
|
726
|
+
if (await closeObserver.wait(closeTimeoutMs))
|
|
727
|
+
return [];
|
|
728
|
+
releaseChildProcessHandles(child);
|
|
729
|
+
return [
|
|
730
|
+
`Could not confirm Windows process-tree termination within ${closeTimeoutMs}ms after taskkill; ` +
|
|
731
|
+
`process ${child.pid} may still be running.`,
|
|
732
|
+
];
|
|
733
|
+
}
|
|
734
|
+
catch (error) {
|
|
735
|
+
const diagnostics = [
|
|
736
|
+
`Could not verify Windows process-tree termination after taskkill failure: ${error instanceof Error ? error.message : String(error)}`,
|
|
737
|
+
];
|
|
738
|
+
const usedSigkillFallback = child.exitCode === null && child.signalCode === null;
|
|
739
|
+
if (usedSigkillFallback) {
|
|
740
|
+
try {
|
|
741
|
+
child.kill('SIGKILL');
|
|
742
|
+
}
|
|
743
|
+
catch (killError) {
|
|
744
|
+
diagnostics.push(`Windows SIGKILL fallback failed: ${killError instanceof Error ? killError.message : String(killError)}`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (!(await closeObserver.wait(closeTimeoutMs))) {
|
|
748
|
+
diagnostics.push(`Could not confirm Windows process-tree termination within ${closeTimeoutMs}ms ` +
|
|
749
|
+
`after taskkill failure${usedSigkillFallback ? ' and SIGKILL fallback' : ''}; ` +
|
|
750
|
+
`process ${child.pid} may still be running.`);
|
|
751
|
+
releaseChildProcessHandles(child);
|
|
752
|
+
}
|
|
753
|
+
return diagnostics;
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
closeObserver.dispose();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
export function createWindowsChildProcessTreeMonitor(child, options = {}) {
|
|
760
|
+
return {
|
|
761
|
+
terminate: () => {
|
|
762
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
763
|
+
return Promise.resolve([
|
|
764
|
+
'Skipped Windows process-tree termination after the root exited to avoid signaling ' +
|
|
765
|
+
'a potentially reused PID; detached descendants require Job Object containment.',
|
|
766
|
+
]);
|
|
767
|
+
}
|
|
768
|
+
return terminateWindowsProcessTree(child, options);
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
export async function terminateChildProcessTree(child) {
|
|
773
|
+
if (child.pid === undefined)
|
|
774
|
+
return [];
|
|
775
|
+
if (process.platform === 'win32')
|
|
776
|
+
return terminateWindowsProcessTree(child);
|
|
777
|
+
return terminatePosixProcessTree(child.pid);
|
|
778
|
+
}
|
|
779
|
+
export function createChildProcessTreeMonitor(child, options = {}) {
|
|
780
|
+
if (child.pid === undefined) {
|
|
781
|
+
return { terminate: () => terminateChildProcessTree(child) };
|
|
782
|
+
}
|
|
783
|
+
if (process.platform === 'win32') {
|
|
784
|
+
return createWindowsChildProcessTreeMonitor(child);
|
|
785
|
+
}
|
|
786
|
+
return createPosixProcessTreeMonitor(child.pid, {
|
|
787
|
+
treeId: options.treeId,
|
|
788
|
+
canUseProcessGroup: () => child.exitCode === null && child.signalCode === null,
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
export async function assertProcessTreeInspectionAvailable(options = {}) {
|
|
792
|
+
const platform = options.platform ?? process.platform;
|
|
793
|
+
if (platform === 'win32') {
|
|
794
|
+
throw new Error('Managed Windows descendant cleanup requires Job Object containment, which is not available.');
|
|
795
|
+
}
|
|
796
|
+
try {
|
|
797
|
+
await readPosixProcessTable();
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
throw new Error(`Cannot start a Codex eval process because POSIX process-tree inspection is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=codex-eval-process-tree.js.map
|