@runuai/host 0.9.159 → 0.9.161
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/lib/managed-runtime.ts
CHANGED
|
@@ -1004,6 +1004,11 @@ async function managedInstallState(paths: ManagedRuntimePaths): Promise<ManagedI
|
|
|
1004
1004
|
] as const) {
|
|
1005
1005
|
try {
|
|
1006
1006
|
await lstat(state);
|
|
1007
|
+
// ensureManagedDirectories (a capability-floor claim) creates the bare
|
|
1008
|
+
// runtime directory on an npm install too. Empty, it holds no
|
|
1009
|
+
// generation: still no managed install. Every npm-installed task
|
|
1010
|
+
// machine sat pending on this (2026-10-20).
|
|
1011
|
+
if (state === paths.runtime && (await readdir(state)).length === 0) continue;
|
|
1007
1012
|
return { kind: "damaged", detail: `the managed runtime has a ${what} but no current runtime; repair it first` };
|
|
1008
1013
|
} catch (error) {
|
|
1009
1014
|
if (!isMissing(error)) throw error;
|
|
@@ -22,8 +22,11 @@ import { join } from "node:path";
|
|
|
22
22
|
/** Image label carrying the build-context content hash (rebuild trigger). */
|
|
23
23
|
export const CONTEXT_HASH_LABEL = "com.runuai.context-hash";
|
|
24
24
|
|
|
25
|
-
/** The public registry repository CI publishes the prebuilt image to.
|
|
26
|
-
|
|
25
|
+
/** The public registry repository CI publishes the prebuilt image to. The
|
|
26
|
+
* GitHub org was renamed runuai → moondeckio on 2026-10-20; GHCR, unlike
|
|
27
|
+
* github.com, does not redirect the old owner (pushes fail "owner not
|
|
28
|
+
* found", pulls 404). */
|
|
29
|
+
export const STANDARD_IMAGE_RELEASE_REF = "ghcr.io/moondeckio/uai-standard";
|
|
27
30
|
|
|
28
31
|
/** Pin file basename; packaged at `agent/images/<basename>` in the payload,
|
|
29
32
|
* one directory above the build context so it never perturbs the hash. */
|
|
@@ -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
|
|