@outerlayer/cli 0.5.0 → 0.5.1

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.
Files changed (27) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/build-info.json +1 -1
  3. package/dist/{check-AXCXDYO2.js → check-4Q33B5ZE.js} +10 -8
  4. package/dist/{chunk-5Y3QQRUH.js → chunk-3DD6AOWC.js} +37 -0
  5. package/dist/{chunk-OHFDWLJP.js → chunk-CHLVJNTE.js} +3 -3
  6. package/dist/{chunk-L24G4PLP.js → chunk-F4NU4H3V.js} +399 -18
  7. package/dist/{chunk-ZLB7GCTY.js → chunk-H4CK2YK4.js} +4 -4
  8. package/dist/{chunk-RJ467PDQ.js → chunk-RTXKYLCA.js} +1 -1
  9. package/dist/{chunk-4NMBDRBS.js → chunk-T52PKE2Y.js} +142 -125
  10. package/dist/chunk-UGP76IZ5.js +34 -0
  11. package/dist/{chunk-RWOTHXYE.js → chunk-UWH5DSUP.js} +27 -15
  12. package/dist/{chunk-FC7TMRNA.js → chunk-W7UTFZDH.js} +2 -2
  13. package/dist/chunk-YYYRBYWG.js +32 -0
  14. package/dist/{chunk-QDQEUVUF.js → chunk-ZXBITXTS.js} +1 -1
  15. package/dist/{cli-NNWPFP5S.js → cli-LHSUYYMI.js} +29 -24
  16. package/dist/{config-XVJYZ4IQ.js → config-HL2AIR3K.js} +1 -1
  17. package/dist/{doctor-J3GIMR7K.js → doctor-MOA3RIEP.js} +19 -5
  18. package/dist/index.js +1 -1
  19. package/dist/{init-CZX7ZDWF.js → init-2IPOIXJV.js} +4 -4
  20. package/dist/{init-3ZQLFPNC.js → init-FYDJAKTM.js} +3 -3
  21. package/dist/{init-cmd-W5NOLQPX.js → init-cmd-KLIDRVXQ.js} +1 -1
  22. package/dist/{loop-VGM772I7.js → loop-NEBUGD2Q.js} +7 -5
  23. package/dist/{machine-5WEJA36F.js → machine-ECU3QCZD.js} +2 -2
  24. package/dist/record-C43U4PKS.js +7 -0
  25. package/dist/{status-KBDVUYQ3.js → status-CLATHCTQ.js} +22 -4
  26. package/dist/{work-claim-cmd-BQPPURBW.js → work-claim-cmd-3OPWA77E.js} +2 -2
  27. package/package.json +1 -1
@@ -1004,7 +1004,7 @@ function doctorExitCode(checks) {
1004
1004
 
1005
1005
  // src/doctor-report.ts
1006
1006
  async function gatherDoctorChecks() {
1007
- const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-XVJYZ4IQ.js');
1007
+ const { defaultRunnerConfigPath, hasRunnerBlock, loadRunnerConfig, runnerVmOf } = await import('./config-HL2AIR3K.js');
1008
1008
  const configPath = defaultRunnerConfigPath();
1009
1009
  const checks = runDoctor({ runnerHost: hasRunnerBlock(configPath) });
1010
1010
  const credential = await credentialChecks();
@@ -1016,7 +1016,7 @@ async function gatherDoctorChecks() {
1016
1016
  const { runnerVmCheck } = await import('./doctor-53F2PYY7.js');
1017
1017
  checks.push(runnerVmCheck(configPath, { lima: realLima(), home: homeFromEnv(), instance: runnerVm.instance }));
1018
1018
  } else if (hasRunnerBlock(configPath)) {
1019
- const { runnerHostChecks } = await import('./doctor-J3GIMR7K.js');
1019
+ const { runnerHostChecks } = await import('./doctor-MOA3RIEP.js');
1020
1020
  const hooks = (() => {
1021
1021
  try {
1022
1022
  return loadRunnerConfig(configPath).hooks;
@@ -1041,8 +1041,8 @@ async function gatherDoctorChecks() {
1041
1041
  })();
1042
1042
  const runner = (() => {
1043
1043
  try {
1044
- const { concurrency, build } = loadRunnerConfig(configPath);
1045
- return { concurrency, buildMemory: build?.memory ?? "8g" };
1044
+ const { concurrency, build, housekeeping } = loadRunnerConfig(configPath);
1045
+ return { concurrency, buildMemory: build?.memory ?? "8g", minFreeDisk: housekeeping?.minFreeDisk };
1046
1046
  } catch {
1047
1047
  return void 0;
1048
1048
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { MIN_ENGINE_MAJOR } from './chunk-4NMBDRBS.js';
3
+ import { MIN_ENGINE_MAJOR } from './chunk-T52PKE2Y.js';
4
4
  import { firecrackerArch, MICROVM_ARCHES, guestAssetsPinned } from './chunk-Z62HNKEJ.js';
5
5
  import { spawnSync } from 'child_process';
6
6
  import { existsSync, accessSync, constants, statSync, readFileSync } from 'fs';
@@ -14,11 +14,11 @@ import { defaultGitRunner, materializeContext } from './chunk-XDDW4FRS.js';
14
14
  import { readContextManifest, readSourceCache } from './chunk-WIOZAJ2W.js';
15
15
  import { signingFetchFor } from './chunk-BCLJSQCV.js';
16
16
  import { signerFor } from './chunk-M5POMKKW.js';
17
- import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
18
- import { relative, join, dirname, basename } from 'path';
17
+ import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, runnerDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
18
+ import { relative, join, dirname, resolve, basename } from 'path';
19
19
  import { spawnSync, spawn } from 'child_process';
20
- import { randomBytes, createHash } from 'crypto';
21
- import { existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, renameSync, lstatSync, readFileSync, unlinkSync, readdirSync, openSync, closeSync, mkdtempSync, createWriteStream, statSync, createReadStream } from 'fs';
20
+ import { createHash, randomBytes } from 'crypto';
21
+ import { existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, realpathSync, renameSync, lstatSync, readFileSync, unlinkSync, readdirSync, openSync, closeSync, mkdtempSync, createWriteStream, statSync, createReadStream } from 'fs';
22
22
  import { createServer, request } from 'http';
23
23
  import { request as request$1 } from 'https';
24
24
  import { Transform } from 'stream';
@@ -86,7 +86,7 @@ function isRetryable(err) {
86
86
  }
87
87
  async function retrySend(send, deps = {}) {
88
88
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
89
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
89
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
90
90
  const windowMs = deps.windowMs ?? RETRY_WINDOW_MS;
91
91
  const startedMs = now().getTime();
92
92
  let attempts = 0;
@@ -356,6 +356,119 @@ function lifecycleSteps(lifecycle) {
356
356
  function lifecycleFailureReason(step, exitCode) {
357
357
  return `lifecycle command ${step.name} (${step.source}) exited ${exitCode}`;
358
358
  }
359
+ var BUILD_LABEL = "ai.outerlayer.build";
360
+ var RUNNER_LABEL = "ai.outerlayer.runner";
361
+ function resolvedRunnerDir(configPath) {
362
+ try {
363
+ return realpathSync(runnerDir(configPath));
364
+ } catch {
365
+ try {
366
+ return join(realpathSync(dirname(resolve(configPath))), "runner");
367
+ } catch {
368
+ return resolve(runnerDir(configPath));
369
+ }
370
+ }
371
+ }
372
+ function runnerLabelValue(configPath) {
373
+ return createHash("sha256").update(resolvedRunnerDir(configPath)).digest("hex").slice(0, 12);
374
+ }
375
+ function hardened(input) {
376
+ const { memory, cpus, pids } = input.limits;
377
+ return [
378
+ "--label",
379
+ `${BUILD_LABEL}=${input.buildId}`,
380
+ "--label",
381
+ `${RUNNER_LABEL}=${input.runnerId}`,
382
+ "--network",
383
+ "none",
384
+ "--cap-drop",
385
+ "ALL",
386
+ "--security-opt",
387
+ "no-new-privileges",
388
+ "--read-only",
389
+ "--user",
390
+ input.user,
391
+ // The same figure for swap as for memory leaves the container none.
392
+ "--memory",
393
+ memory,
394
+ "--memory-swap",
395
+ memory,
396
+ "--cpus",
397
+ String(cpus),
398
+ "--pids-limit",
399
+ String(pids)
400
+ ];
401
+ }
402
+ function mountPath(path) {
403
+ if (/[",]/.test(path)) throw new Error(`${path} holds a comma or a quote, which a Docker mount cannot carry; move the runner's config file`);
404
+ return path;
405
+ }
406
+ function containerRunArgs(input) {
407
+ return [
408
+ "--detach",
409
+ "--name",
410
+ input.container,
411
+ ...hardened(input),
412
+ "--mount",
413
+ `type=volume,src=${input.volume},dst=/build`,
414
+ // `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
415
+ "--mount",
416
+ `type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
417
+ "--mount",
418
+ `type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
419
+ "--entrypoint",
420
+ "/opt/outerlayer/bin/tini",
421
+ input.image,
422
+ "--",
423
+ "/opt/outerlayer/bin/node",
424
+ "/opt/outerlayer/cli/dist/index.js",
425
+ "broker-relay",
426
+ "--port",
427
+ String(RELAY_PORT),
428
+ "--socket",
429
+ `${JOB_MOUNT}/tunnel.sock`,
430
+ ...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
431
+ "--forward",
432
+ `${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
433
+ ]).flat(),
434
+ ...[
435
+ "--forward",
436
+ `${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
437
+ "--forward",
438
+ `${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
439
+ ]
440
+ ];
441
+ }
442
+ function prepareRunArgs(input) {
443
+ return [
444
+ "--rm",
445
+ ...hardened(input),
446
+ "--mount",
447
+ `type=volume,src=${input.volume},dst=/build`,
448
+ "--entrypoint",
449
+ "mkdir",
450
+ input.image,
451
+ "-p",
452
+ "-m",
453
+ "1777",
454
+ "/build/tmp",
455
+ "/build/home",
456
+ "/build/work"
457
+ ];
458
+ }
459
+ function containerExecArgs(input) {
460
+ const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
461
+ const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
462
+ return [
463
+ "-u",
464
+ input.user,
465
+ ...input.workdir === void 0 ? [] : ["-w", input.workdir],
466
+ ...literals,
467
+ ...passed,
468
+ input.container,
469
+ ...input.argv
470
+ ];
471
+ }
359
472
  var AssetError = class extends Error {
360
473
  constructor(file, message) {
361
474
  super(message);
@@ -496,7 +609,7 @@ function converterArgs(dest, extra, script) {
496
609
  }
497
610
  var firstLine2 = (text) => text.trim().split("\n")[0] ?? "";
498
611
  function exportInto(container, converter) {
499
- return new Promise((resolve, reject) => {
612
+ return new Promise((resolve2, reject) => {
500
613
  const exporter = spawn("docker", ["export", container], { stdio: ["ignore", "pipe", "pipe"], env: childEnvironment(process.env) });
501
614
  const writer = spawn("docker", converter, { stdio: ["pipe", "ignore", "pipe"], env: childEnvironment(process.env) });
502
615
  const errors = [];
@@ -513,7 +626,7 @@ function exportInto(container, converter) {
513
626
  if (code !== 0 && failure === void 0) failure = `${name} exited ${code}`;
514
627
  exits += 1;
515
628
  if (exits < 2) return;
516
- if (failure === void 0) resolve();
629
+ if (failure === void 0) resolve2();
517
630
  else reject(new Error(`${failure}: ${firstLine2(errors.join("\n"))}`));
518
631
  };
519
632
  exporter.once("exit", (code) => done("docker export", code));
@@ -660,7 +773,7 @@ function createFirecrackerHost(options = {}) {
660
773
  const failed = new Promise((_resolve, reject) => child.once("error", reject));
661
774
  const pid = child.pid;
662
775
  if (pid === void 0) return await failed;
663
- exits.set(pid, new Promise((resolve) => child.once("exit", () => resolve())));
776
+ exits.set(pid, new Promise((resolve2) => child.once("exit", () => resolve2())));
664
777
  child.unref();
665
778
  const start = startTimeOf(pid);
666
779
  if (start === null) throw new Error(`${command} exited as soon as it started; its console is at ${input.consolePath}`);
@@ -677,8 +790,8 @@ function createFirecrackerHost(options = {}) {
677
790
  }
678
791
  const exited = exits.get(vm.pid);
679
792
  const deadline = Date.now() + STOP_WAIT_MS;
680
- if (exited !== void 0) await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, STOP_WAIT_MS))]);
681
- while (alive(vm) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, POLL_MS));
793
+ if (exited !== void 0) await Promise.race([exited, new Promise((resolve2) => setTimeout(resolve2, STOP_WAIT_MS))]);
794
+ while (alive(vm) && Date.now() < deadline) await new Promise((resolve2) => setTimeout(resolve2, POLL_MS));
682
795
  exits.delete(vm.pid);
683
796
  },
684
797
  alive
@@ -688,7 +801,7 @@ function createFirecrackerHost(options = {}) {
688
801
  // src/runner/microvm/host.ts
689
802
  function createMicroVmPorts(over = {}) {
690
803
  return {
691
- assets: over.assets ?? ((runnerDir) => ensureAssets({ runnerDir })),
804
+ assets: over.assets ?? ((runnerDir2) => ensureAssets({ runnerDir: runnerDir2 })),
692
805
  rootfs: over.rootfs ?? createRootfsHost(),
693
806
  firecracker: over.firecracker ?? createFirecrackerHost(),
694
807
  exec: over.exec ?? execCapture
@@ -845,104 +958,6 @@ function removeStagedContext(buildDir2) {
845
958
  rmSync(stagedContextDir(buildDir2), { recursive: true, force: true });
846
959
  }
847
960
 
848
- // src/runner/container/run-args.ts
849
- var BUILD_LABEL = "ai.outerlayer.build";
850
- function hardened(input) {
851
- const { memory, cpus, pids } = input.limits;
852
- return [
853
- "--label",
854
- `${BUILD_LABEL}=${input.buildId}`,
855
- "--network",
856
- "none",
857
- "--cap-drop",
858
- "ALL",
859
- "--security-opt",
860
- "no-new-privileges",
861
- "--read-only",
862
- "--user",
863
- input.user,
864
- // The same figure for swap as for memory leaves the container none.
865
- "--memory",
866
- memory,
867
- "--memory-swap",
868
- memory,
869
- "--cpus",
870
- String(cpus),
871
- "--pids-limit",
872
- String(pids)
873
- ];
874
- }
875
- function mountPath(path) {
876
- if (/[",]/.test(path)) throw new Error(`${path} holds a comma or a quote, which a Docker mount cannot carry; move the runner's config file`);
877
- return path;
878
- }
879
- function containerRunArgs(input) {
880
- return [
881
- "--detach",
882
- "--name",
883
- input.container,
884
- ...hardened(input),
885
- "--mount",
886
- `type=volume,src=${input.volume},dst=/build`,
887
- // `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
888
- "--mount",
889
- `type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
890
- "--mount",
891
- `type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
892
- "--entrypoint",
893
- "/opt/outerlayer/bin/tini",
894
- input.image,
895
- "--",
896
- "/opt/outerlayer/bin/node",
897
- "/opt/outerlayer/cli/dist/index.js",
898
- "broker-relay",
899
- "--port",
900
- String(RELAY_PORT),
901
- "--socket",
902
- `${JOB_MOUNT}/tunnel.sock`,
903
- ...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
904
- "--forward",
905
- `${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
906
- ]).flat(),
907
- ...[
908
- "--forward",
909
- `${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
910
- "--forward",
911
- `${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
912
- ]
913
- ];
914
- }
915
- function prepareRunArgs(input) {
916
- return [
917
- "--rm",
918
- ...hardened(input),
919
- "--mount",
920
- `type=volume,src=${input.volume},dst=/build`,
921
- "--entrypoint",
922
- "mkdir",
923
- input.image,
924
- "-p",
925
- "-m",
926
- "1777",
927
- "/build/tmp",
928
- "/build/home",
929
- "/build/work"
930
- ];
931
- }
932
- function containerExecArgs(input) {
933
- const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
934
- const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
935
- return [
936
- "-u",
937
- input.user,
938
- ...input.workdir === void 0 ? [] : ["-w", input.workdir],
939
- ...literals,
940
- ...passed,
941
- input.container,
942
- ...input.argv
943
- ];
944
- }
945
-
946
961
  // src/runner/container/docker.ts
947
962
  function createContainerHost(deps = {}) {
948
963
  const run = deps.run ?? spawnRun;
@@ -1027,9 +1042,9 @@ async function git(args, cwd, env, input) {
1027
1042
  if (size < maxOutputBytes) out.push(chunk);
1028
1043
  size += chunk.length;
1029
1044
  });
1030
- const closed = new Promise((resolve, reject) => {
1045
+ const closed = new Promise((resolve2, reject) => {
1031
1046
  child.once("error", reject);
1032
- child.once("close", (status) => resolve(status));
1047
+ child.once("close", (status) => resolve2(status));
1033
1048
  });
1034
1049
  if (input !== void 0) {
1035
1050
  await pipeline(input, child.stdin).catch(() => void 0);
@@ -1226,14 +1241,14 @@ async function drain(req) {
1226
1241
  }
1227
1242
  }
1228
1243
  function readPrefix(req, gzip) {
1229
- return new Promise((resolve, reject) => {
1244
+ return new Promise((resolve2, reject) => {
1230
1245
  const held = [];
1231
1246
  const finish = (section) => {
1232
1247
  req.off("data", onData);
1233
1248
  req.off("end", onEnd);
1234
1249
  req.off("error", reject);
1235
1250
  req.pause();
1236
- resolve({ held, section });
1251
+ resolve2({ held, section });
1237
1252
  };
1238
1253
  const onData = (chunk) => {
1239
1254
  held.push(chunk);
@@ -1247,19 +1262,19 @@ function readPrefix(req, gzip) {
1247
1262
  });
1248
1263
  }
1249
1264
  function readBody(req, limit) {
1250
- return new Promise((resolve, reject) => {
1265
+ return new Promise((resolve2, reject) => {
1251
1266
  const chunks = [];
1252
1267
  let size = 0;
1253
1268
  req.on("data", (chunk) => {
1254
1269
  size += chunk.length;
1255
1270
  if (size > limit) {
1256
1271
  req.pause();
1257
- resolve(void 0);
1272
+ resolve2(void 0);
1258
1273
  return;
1259
1274
  }
1260
1275
  chunks.push(chunk);
1261
1276
  });
1262
- req.once("end", () => resolve(Buffer.concat(chunks)));
1277
+ req.once("end", () => resolve2(Buffer.concat(chunks)));
1263
1278
  req.once("error", reject);
1264
1279
  });
1265
1280
  }
@@ -1272,7 +1287,7 @@ function createGitRemote(options) {
1272
1287
  const { route, res } = input;
1273
1288
  const url = new URL(`${gitUrl}/${route.owner}/${route.name}.git/${route.kind === "info-refs" ? "info/refs" : route.kind === "lfs-batch" ? "info/lfs/objects/batch" : `git-${route.kind}`}${route.search === "" ? "" : `?${route.search}`}`);
1274
1289
  const send = url.protocol === "https:" ? request$1 : request;
1275
- return new Promise((resolve) => {
1290
+ return new Promise((resolve2) => {
1276
1291
  const upstream = send(url, { method: input.method, headers: { ...input.headers, connection: "close" } }, (answer) => {
1277
1292
  const headers = {};
1278
1293
  for (const [name, value] of Object.entries(answer.headers)) {
@@ -1294,18 +1309,18 @@ function createGitRemote(options) {
1294
1309
  });
1295
1310
  answer.once("end", () => {
1296
1311
  res.end();
1297
- resolve();
1312
+ resolve2();
1298
1313
  });
1299
1314
  answer.once("error", () => {
1300
1315
  res.destroy();
1301
- resolve();
1316
+ resolve2();
1302
1317
  });
1303
1318
  });
1304
1319
  upstream.on("error", (err) => {
1305
1320
  log(`could not reach GitHub for ${route.kind}: ${err.message}`);
1306
1321
  if (res.headersSent) res.destroy();
1307
1322
  else plain(res, 502, "the runner could not reach GitHub\n");
1308
- resolve();
1323
+ resolve2();
1309
1324
  });
1310
1325
  res.once("close", () => upstream.destroy());
1311
1326
  input.feed(upstream);
@@ -1594,7 +1609,7 @@ async function startTunnel(socketPath, opts = {}) {
1594
1609
  if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH_BYTES) {
1595
1610
  throw new Error(`${socketPath} is too long for a Unix socket; keep the runner's config file nearer the root of the filesystem`);
1596
1611
  }
1597
- const resolve = opts.resolve ?? systemResolve;
1612
+ const resolve2 = opts.resolve ?? systemResolve;
1598
1613
  const openConnection = opts.connect ?? ((t) => connect({ host: t.address, port: t.port }));
1599
1614
  const ownAddresses = opts.hostAddresses ?? systemHostAddresses;
1600
1615
  const connectTimeoutMs = opts.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
@@ -1678,7 +1693,7 @@ async function startTunnel(socketPath, opts = {}) {
1678
1693
  }
1679
1694
  let addresses;
1680
1695
  try {
1681
- addresses = await resolve(host);
1696
+ addresses = await resolve2(host);
1682
1697
  } catch {
1683
1698
  return { ok: false, status: 502, body: `could not resolve ${host}
1684
1699
  ` };
@@ -1863,7 +1878,7 @@ function createContainerRuntime(opts = {}) {
1863
1878
  )),
1864
1879
  startTunnel: opts.startTunnel ?? startTunnel,
1865
1880
  tunnelOptions: opts.tunnelOptions,
1866
- sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
1881
+ sleep: opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))),
1867
1882
  hostEnv: opts.hostEnv ?? process.env,
1868
1883
  credentials: opts.credentials ?? /* @__PURE__ */ new Map(),
1869
1884
  github: { gitUrl: opts.github?.gitUrl ?? "https://github.com", apiUrl: opts.github?.apiUrl ?? "https://api.github.com" },
@@ -2035,11 +2050,13 @@ async function provisionContainer(input) {
2035
2050
  return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
2036
2051
  }
2037
2052
  const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
2038
- const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId });
2053
+ const runnerId = runnerLabelValue(config.configPath);
2054
+ const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
2039
2055
  if (volume.status !== 0) return dockerFailed("creating the build's volume", volume);
2040
2056
  if (input.aborted()) return bail("", true);
2041
2057
  const runInput = {
2042
2058
  buildId,
2059
+ runnerId,
2043
2060
  container: name,
2044
2061
  volume: name,
2045
2062
  buildDir: buildDir2,
@@ -2230,4 +2247,4 @@ async function cleanupContainer(runtime, state, jobDir) {
2230
2247
  if (failures.length > 0) throw new Error(failures.join("; "));
2231
2248
  }
2232
2249
 
2233
- export { ADDED_USER, AgentUserError, AssetError, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, classifyRefusal, cleanupContainer, containerCommandLine, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, shellQuote, shortClaim, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
2250
+ export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, shellQuote, shortClaim, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
4
+ import { readFileSync, mkdirSync, writeFileSync, renameSync } from 'fs';
5
+ import { join } from 'path';
6
+
7
+ createRequire(import.meta.url);
8
+ var FILE = "housekeeping.json";
9
+ function readHousekeepingRecord(runnerDirPath) {
10
+ const path = join(runnerDirPath, FILE);
11
+ try {
12
+ const raw = JSON.parse(readFileSync(path, "utf8"));
13
+ const record = {};
14
+ if (typeof raw.lastRunAt === "string") record.lastRunAt = raw.lastRunAt;
15
+ if (typeof raw.freedBytes === "number") record.freedBytes = raw.freedBytes;
16
+ const holding = raw.holding;
17
+ if (typeof holding?.free === "number" && typeof holding.needed === "number" && typeof holding.since === "string") {
18
+ record.holding = { free: holding.free, needed: holding.needed, since: holding.since };
19
+ }
20
+ return record;
21
+ } catch {
22
+ return {};
23
+ }
24
+ }
25
+ function writeHousekeepingRecord(runnerDirPath, patch) {
26
+ const merged = { ...readHousekeepingRecord(runnerDirPath), ...patch };
27
+ mkdirSync(runnerDirPath, { recursive: true, mode: RUNNER_DIR_MODE });
28
+ const path = join(runnerDirPath, FILE);
29
+ const temp = `${path}.partial-${process.pid}`;
30
+ writeFileSync(temp, JSON.stringify(merged, null, 2), { mode: RUNNER_FILE_MODE });
31
+ renameSync(temp, path);
32
+ }
33
+
34
+ export { readHousekeepingRecord, writeHousekeepingRecord };
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from 'node:module';
3
3
  import { DOCKERD_LOG } from './chunk-CBPPJSAR.js';
4
4
  import { installScrubContext } from './chunk-ZOC7JSYC.js';
5
- import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, ADDED_USER, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, GH_CONFIG_YML, ghHostsYml, isLiteralAddress, deleteBuildDisk } from './chunk-4NMBDRBS.js';
5
+ import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, ADDED_USER, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, GH_CONFIG_YML, ghHostsYml, isLiteralAddress, deleteBuildDisk } from './chunk-T52PKE2Y.js';
6
6
  import { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
7
7
  import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
8
8
  import { childEnvironment } from './chunk-774HEQL6.js';
@@ -11,7 +11,7 @@ import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-B2G7JHH
11
11
  import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-RMAZ2NWA.js';
12
12
  import { planArtifactUpload } from './chunk-WOBNRVQT.js';
13
13
  import { scrubText } from './chunk-EX6M5JPR.js';
14
- import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-5Y3QQRUH.js';
14
+ import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
15
15
  import { parseSize } from './chunk-F6GFZXZ3.js';
16
16
  import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT } from './chunk-AUFG23AA.js';
17
17
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
@@ -410,6 +410,8 @@ async function stopOrphanedMicroVms(input) {
410
410
  process.kill(-job.pid, "SIGKILL");
411
411
  } catch {
412
412
  }
413
+ const deadline = Date.now() + 2e3;
414
+ while (isSameProcess(job.pid, job.start) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 10));
413
415
  }
414
416
  if (state.firecracker !== void 0 && input.runtime.vm.firecracker.alive(state.firecracker)) {
415
417
  await input.runtime.vm.firecracker.stop(state.firecracker);
@@ -581,18 +583,6 @@ async function closeHostBuild(runtime, build) {
581
583
  rmSync(build.buildDir, { recursive: true, force: true });
582
584
  if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
583
585
  }
584
- function readJobFile(path) {
585
- if (!existsSync(path)) return null;
586
- try {
587
- return JSON.parse(readFileSync(path, "utf8"));
588
- } catch {
589
- return null;
590
- }
591
- }
592
- function writeJobFile(path, job) {
593
- mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
594
- writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
595
- }
596
586
  var ENDED = "ended before provision finished";
597
587
  async function provisionProcess(input) {
598
588
  const { config, runtime, item, jobDir } = input;
@@ -635,6 +625,28 @@ async function provisionProcess(input) {
635
625
  function cleanupProcess(runtime, state) {
636
626
  return closeHostBuild(runtime, state);
637
627
  }
628
+ function readJobFile(path) {
629
+ if (!existsSync(path)) return null;
630
+ try {
631
+ return JSON.parse(readFileSync(path, "utf8"));
632
+ } catch {
633
+ return null;
634
+ }
635
+ }
636
+ function readJobFileChecked(path) {
637
+ if (!existsSync(path)) return { kind: "missing" };
638
+ try {
639
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
640
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { kind: "unreadable" };
641
+ return { kind: "ok", job: parsed };
642
+ } catch {
643
+ return { kind: "unreadable" };
644
+ }
645
+ }
646
+ function writeJobFile(path, job) {
647
+ mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
648
+ writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
649
+ }
638
650
 
639
651
  // src/runner/verdict.ts
640
652
  var VERDICT_SETTLE_MS = 5 * 6e4;
@@ -1458,4 +1470,4 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
1458
1470
  return recovered;
1459
1471
  }
1460
1472
 
1461
- export { LEASE_SECONDS, RENEW_INTERVAL_SECONDS, deliverCommitHooks, describeHookEnd, execArgv, execCommandLine, finishJob, hookLimitsFor, hostCommandEnvironment, hostEnvironment, isGroupAlive, isIdle, isTimedOut, isUnfinished, jobEnv, listAdoptableJobs, listenOnBroker, microVmCommandLine, needsRenew, outcomeFromExitCode, provisionEnvironment, provisionMicroVm, provisionProcess, readCpuUsec, readJobFile, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, stopOrphanedMicroVms, unfinishedJobs, workdirProblem, writeJobFile };
1473
+ export { ALREADY_RELEASED, LEASE_SECONDS, RENEW_INTERVAL_SECONDS, deliverCommitHooks, describeHookEnd, execArgv, execCommandLine, finishJob, hookLimitsFor, hostCommandEnvironment, hostEnvironment, isGroupAlive, isIdle, isTimedOut, isUnfinished, jobEnv, listAdoptableJobs, listenOnBroker, microVmCommandLine, needsRenew, outcomeFromExitCode, provisionEnvironment, provisionMicroVm, provisionProcess, readCpuUsec, readJobFile, readJobFileChecked, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, stopOrphanedMicroVms, unfinishedJobs, workdirProblem, writeJobFile };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { detectMachine, supportsContainerBuilds, reachDockerStep, describeMachineDocker, supportsMicroVmBuilds } from './chunk-RJ467PDQ.js';
4
- import { MIN_ENGINE_MAJOR } from './chunk-4NMBDRBS.js';
3
+ import { detectMachine, supportsContainerBuilds, reachDockerStep, describeMachineDocker, supportsMicroVmBuilds } from './chunk-RTXKYLCA.js';
4
+ import { MIN_ENGINE_MAJOR } from './chunk-T52PKE2Y.js';
5
5
  import { RUNNER_DIR_MODE, runnerDir, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
6
6
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
7
7
  import { dirname } from 'path';
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { statfsSync } from 'fs';
4
+ import { dirname } from 'path';
5
+
6
+ createRequire(import.meta.url);
7
+ function freeBytes(path, statfs = statfsSync) {
8
+ let at = path;
9
+ for (; ; ) {
10
+ try {
11
+ const stats = statfs(at);
12
+ return Number(stats.bavail) * Number(stats.bsize);
13
+ } catch (err) {
14
+ if (err.code !== "ENOENT") return void 0;
15
+ const parent = dirname(at);
16
+ if (parent === at) return void 0;
17
+ at = parent;
18
+ }
19
+ }
20
+ }
21
+ var UNITS = ["B", "KB", "MB", "GB", "TB"];
22
+ function formatBytes(bytes) {
23
+ let value = Math.max(0, bytes);
24
+ let unit = 0;
25
+ while (value >= 1024 && unit < UNITS.length - 1) {
26
+ value /= 1024;
27
+ unit++;
28
+ }
29
+ return unit === 0 ? `${Math.round(value)} B` : `${value.toFixed(1)} ${UNITS[unit]}`;
30
+ }
31
+
32
+ export { formatBytes, freeBytes };
@@ -4,6 +4,6 @@ import { createRequire } from 'node:module';
4
4
  createRequire(import.meta.url);
5
5
 
6
6
  // src/runner/protocol.ts
7
- var RUNNER_PROTOCOL = 12;
7
+ var RUNNER_PROTOCOL = 13;
8
8
 
9
9
  export { RUNNER_PROTOCOL };