@runuai/host 0.9.159 → 0.9.160
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.
|
@@ -243,6 +243,77 @@ export function taskMachinePeerProcessIds(): number[] {
|
|
|
243
243
|
);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
/** A process's parent from /proc; null once the process is gone. */
|
|
247
|
+
export function processParentId(pid: number): number | null {
|
|
248
|
+
let stat: string;
|
|
249
|
+
try {
|
|
250
|
+
stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
// `pid (comm) state ppid ...`: comm may itself hold spaces or parens.
|
|
256
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
257
|
+
if (!Number.isSafeInteger(ppid) || ppid < 0) {
|
|
258
|
+
throw new Error(`process ${pid} has an unreadable parent`);
|
|
259
|
+
}
|
|
260
|
+
return ppid;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Every process in the sidecar service cgroup that this runner did not start.
|
|
265
|
+
* The fresh-incarnation proof asks this, not `taskMachinePeerProcessIds`: the
|
|
266
|
+
* runner's own helpers share its cgroup from boot, tsx's esbuild service above
|
|
267
|
+
* all (a long-lived child of every runner that compiled a module; live
|
|
268
|
+
* 2026-10-20, every marked machine stayed pending "not quiescent" on it), and
|
|
269
|
+
* a pending runner admits no task, so nothing it started is a task process.
|
|
270
|
+
* What an earlier runner process left behind (KillMode=process) was reparented
|
|
271
|
+
* when that process ended: it is never this runner's descendant, so it counts.
|
|
272
|
+
*/
|
|
273
|
+
export function taskMachineForeignProcessIds(): number[] {
|
|
274
|
+
return foreignProcessIdsWith({
|
|
275
|
+
list: taskMachinePeerProcessIds,
|
|
276
|
+
parent: processParentId,
|
|
277
|
+
self: process.pid,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface ForeignProcessDeps {
|
|
282
|
+
list(): readonly number[];
|
|
283
|
+
parent(pid: number): number | null;
|
|
284
|
+
self: number;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Exported seam keeps the ancestry walk executable in tests. */
|
|
288
|
+
export function foreignProcessIdsWith(deps: ForeignProcessDeps): number[] {
|
|
289
|
+
const foreign: number[] = [];
|
|
290
|
+
for (const pid of deps.list()) {
|
|
291
|
+
if (pid === deps.self) continue;
|
|
292
|
+
const seen = new Set<number>();
|
|
293
|
+
let current: number | null = pid;
|
|
294
|
+
let ancestry: "ours" | "foreign" | "gone" = "foreign";
|
|
295
|
+
for (;;) {
|
|
296
|
+
const parent = deps.parent(current);
|
|
297
|
+
if (parent === null) {
|
|
298
|
+
// The process itself is gone: nothing to count. An ancestor gone
|
|
299
|
+
// mid-walk leaves an orphan the next look sees reparented: fail
|
|
300
|
+
// closed now, as that look would.
|
|
301
|
+
if (current === pid) ancestry = "gone";
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (parent === deps.self) {
|
|
305
|
+
ancestry = "ours";
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
if (parent <= 1 || seen.has(parent)) break;
|
|
309
|
+
seen.add(parent);
|
|
310
|
+
current = parent;
|
|
311
|
+
}
|
|
312
|
+
if (ancestry === "foreign") foreign.push(pid);
|
|
313
|
+
}
|
|
314
|
+
return foreign;
|
|
315
|
+
}
|
|
316
|
+
|
|
246
317
|
/** Kill everything in the sidecar service cgroup except this root runner.
|
|
247
318
|
* The task's agents, editor, terminals and sudo children all descend from the
|
|
248
319
|
* service; KillMode=process is precisely why they otherwise survive. */
|
|
@@ -26,6 +26,8 @@ export interface TaskMachineCredentialsActivationDeps {
|
|
|
26
26
|
filesReady(): boolean;
|
|
27
27
|
retirementReady(): boolean;
|
|
28
28
|
taskRows(): readonly { taskId: string }[];
|
|
29
|
+
/** Processes in the sidecar's service cgroup that this runner did not
|
|
30
|
+
* start (its own helpers, such as tsx's esbuild service, never count). */
|
|
29
31
|
peerProcessIds(): readonly number[];
|
|
30
32
|
}
|
|
31
33
|
|
package/package.json
CHANGED
package/src/main.ts
CHANGED
|
@@ -428,7 +428,7 @@ import {
|
|
|
428
428
|
kernelBootId,
|
|
429
429
|
TaskFilesMachineRetirementOutbox,
|
|
430
430
|
TASK_FILES_MACHINE_RETIREMENT_RECORD,
|
|
431
|
-
|
|
431
|
+
taskMachineForeignProcessIds,
|
|
432
432
|
} from "../lib/task-files-machine-retirement-outbox";
|
|
433
433
|
import {
|
|
434
434
|
planFreshTaskMachineCredentialsActivation,
|
|
@@ -1819,7 +1819,8 @@ function taskMachineCredentialsActivationDeps(): TaskMachineCredentialsActivatio
|
|
|
1819
1819
|
.select({ taskId: schema.hostTasks.taskId })
|
|
1820
1820
|
.from(schema.hostTasks)
|
|
1821
1821
|
.all(),
|
|
1822
|
-
|
|
1822
|
+
// Not the runner's own children (tsx's esbuild service): see the helper.
|
|
1823
|
+
peerProcessIds: taskMachineForeignProcessIds,
|
|
1823
1824
|
};
|
|
1824
1825
|
}
|
|
1825
1826
|
|