@outerlayer/cli 0.7.0 → 0.8.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 (74) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +5 -5
  3. package/dist/{agent-setup-TXLDY5RV.js → agent-setup-K6RD2Z4P.js} +4 -4
  4. package/dist/{build-JF5UOWKJ.js → build-MS34XEGN.js} +2 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/builder-IG6FNA54.js +13 -0
  7. package/dist/{check-ASZL5BEO.js → check-GX5ND6RO.js} +26 -22
  8. package/dist/chunk-2BRSMBBB.js +264 -0
  9. package/dist/{chunk-PUN7LMNS.js → chunk-4ACZCY4I.js} +2 -2
  10. package/dist/{chunk-2VO25HHO.js → chunk-4FPWKV7L.js} +3 -3
  11. package/dist/{chunk-AII7CKTH.js → chunk-5ODHQP6O.js} +68 -145
  12. package/dist/chunk-7RQ73DLO.js +23 -0
  13. package/dist/{chunk-7FTNIAHS.js → chunk-AX23QKHS.js} +7 -3
  14. package/dist/{chunk-6ZZHWY2Z.js → chunk-BF4KKEFY.js} +4 -4
  15. package/dist/{chunk-5FWNEQNZ.js → chunk-BQFNZOMQ.js} +1 -1
  16. package/dist/chunk-CX22S5BA.js +45 -0
  17. package/dist/{chunk-D6FXM5OW.js → chunk-GAYWY4GI.js} +8 -8
  18. package/dist/{chunk-MQPRB5K7.js → chunk-HSN5QNS5.js} +8 -8
  19. package/dist/chunk-I2UOAV7X.js +133 -0
  20. package/dist/chunk-I7CJIRMM.js +93 -0
  21. package/dist/{chunk-SSU747O6.js → chunk-ILEA3YHM.js} +1 -1
  22. package/dist/{chunk-OUUINEYB.js → chunk-JKIWMRFH.js} +1 -1
  23. package/dist/{chunk-JPR2WHHF.js → chunk-KSLNQOOS.js} +1 -1
  24. package/dist/{chunk-DGAIVMMN.js → chunk-KV47VQJZ.js} +5 -5
  25. package/dist/{chunk-E4PIFIC3.js → chunk-M4ERCYDB.js} +133 -159
  26. package/dist/chunk-PTW6W5NR.js +127 -0
  27. package/dist/{chunk-DTXOJDEH.js → chunk-PWBILI5P.js} +3 -2
  28. package/dist/{chunk-X2FSFU4C.js → chunk-PXFEYBDE.js} +3 -3
  29. package/dist/{chunk-6IYLDODX.js → chunk-Q4432IZT.js} +5 -5
  30. package/dist/{chunk-AJKEHABI.js → chunk-QB5JQH5V.js} +255 -54
  31. package/dist/{chunk-AQ54MKSQ.js → chunk-QNRSFEGY.js} +2 -2
  32. package/dist/{chunk-774HEQL6.js → chunk-TEKZRTM3.js} +91 -8
  33. package/dist/{chunk-CEBIA5FD.js → chunk-TTNFGMHO.js} +42 -50
  34. package/dist/{chunk-F6GFZXZ3.js → chunk-UXYLWVTQ.js} +1 -18
  35. package/dist/{chunk-M5POMKKW.js → chunk-V7NIVOFD.js} +5 -5
  36. package/dist/{chunk-BCLJSQCV.js → chunk-YO4PSLVB.js} +1 -1
  37. package/dist/{chunk-6QWV3YDJ.js → chunk-Z2THOHPP.js} +2 -2
  38. package/dist/{cli-LEVCGAQV.js → cli-X5ONGYNY.js} +128 -78
  39. package/dist/{command-FLSYCGIB.js → command-7NFU3IPO.js} +38 -20
  40. package/dist/{config-I3TVFYLH.js → config-Z3USDBLU.js} +3 -2
  41. package/dist/{connect-cmd-MJMUONDY.js → connect-cmd-PNZ6IJLJ.js} +2 -2
  42. package/dist/{context-adopt-CE4KDXS3.js → context-adopt-D2Z7F7PB.js} +3 -3
  43. package/dist/{context-materialize-QL33HDJN.js → context-materialize-4PMTDS6K.js} +3 -3
  44. package/dist/{dist-PXBSA7QX.js → dist-3ZTZF2TP.js} +1 -1
  45. package/dist/docker-PRHRSJM5.js +15 -0
  46. package/dist/{doctor-FVDT3KJZ.js → doctor-KALLPCRB.js} +39 -16
  47. package/dist/{emit-artifact-cmd-7RPMRSVP.js → emit-artifact-cmd-D5GMVWNC.js} +7 -7
  48. package/dist/{emit-criteria-cmd-MKI7WETO.js → emit-criteria-cmd-IE7VIM64.js} +5 -5
  49. package/dist/{emit-finding-cmd-RMLGZASB.js → emit-finding-cmd-46PTPN3A.js} +5 -5
  50. package/dist/{emit-result-cmd-MTXAZ2GV.js → emit-result-cmd-KXHHP6HX.js} +7 -7
  51. package/dist/{hook-fast-XBOZ3N6L.js → hook-fast-5H5QBJKT.js} +1 -1
  52. package/dist/{host-key-KDYZ5ECC.js → host-key-YMLXY6QB.js} +1 -1
  53. package/dist/index.js +3 -3
  54. package/dist/{init-VLW7H6RW.js → init-B4CECVOX.js} +25 -18
  55. package/dist/{init-BACXESVV.js → init-S4DH6PWW.js} +16 -13
  56. package/dist/{init-cmd-VJFB5L7X.js → init-cmd-P4MVCBCZ.js} +8 -8
  57. package/dist/{install-NMAPLLBQ.js → install-FHTVJZS6.js} +5 -4
  58. package/dist/{install-cmd-B5SEU3G7.js → install-cmd-BEC7XTNG.js} +3 -1
  59. package/dist/{loop-D3243JKT.js → loop-ODMRE3CW.js} +26 -22
  60. package/dist/{machine-EB3GEVI4.js → machine-J4SAB3Z2.js} +15 -12
  61. package/dist/{mcp-serve-cmd-N6UD2KBX.js → mcp-serve-cmd-CT47P4YQ.js} +2 -2
  62. package/dist/{session-sender-LMACEJJS.js → session-sender-V46VPWQ5.js} +6 -6
  63. package/dist/{status-F4THTJJK.js → status-QBKSH3QE.js} +19 -16
  64. package/dist/{sync-cmd-M6JA3OEE.js → sync-cmd-RJR77V4O.js} +5 -5
  65. package/dist/{watch-NB5TGPEZ.js → watch-YNZTFLEY.js} +5 -5
  66. package/dist/{work-claim-cmd-UHMWOS3G.js → work-claim-cmd-KAD3ZAH7.js} +10 -9
  67. package/dist/{work-cmd-6UMCFTAW.js → work-cmd-CNWPTH7W.js} +6 -6
  68. package/dist/{work-comment-cmd-E4LEUIXZ.js → work-comment-cmd-HH45S6UE.js} +6 -6
  69. package/dist/{work-open-pr-cmd-OPZWGSEQ.js → work-open-pr-cmd-5KR3KFCN.js} +7 -7
  70. package/dist/{work-pr-cmd-IDIAFFRU.js → work-pr-cmd-TTEGCP76.js} +6 -6
  71. package/package.json +2 -2
  72. package/skill-pack/maintained/amend/SKILL.md +22 -6
  73. package/dist/chunk-Q5G2ODUZ.js +0 -54
  74. package/dist/docker-NEGDLU6D.js +0 -7
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { readDefaultBranchHead, cloneRepository, buildRepositoryImage, CLAUDE_CODE_VERSION, firecrackerArch, MICROVM_ARCHES, DEFAULT_BASE_IMAGE, GUEST_HELPER_SHA256, GUEST_ASSETS_BASE_URL, GUEST_KERNEL_SHA256, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_VERSION } from './chunk-CEBIA5FD.js';
3
+ import { readDefaultBranchHead, cloneRepository, buildRepositoryImage } from './chunk-TTNFGMHO.js';
4
4
  import { startTimeOf, isSameProcess } from './chunk-N5FOE5PS.js';
5
5
  import { currentCliBuild } from './chunk-OGFGZQA3.js';
6
- import { createDockerHost, childEnvironment, spawnRun } from './chunk-774HEQL6.js';
6
+ import { createDockerHost, childEnvironment, spawnRun } from './chunk-TEKZRTM3.js';
7
7
  import { RecipeError } from './chunk-QOZKPLVJ.js';
8
+ import { builderSpec, runnerLabelValue, RUNNER_LABEL, BUILD_LABEL, prepareRunArgs, containerRunArgs, containerExecArgs } from './chunk-2BRSMBBB.js';
9
+ import { CLAUDE_CODE_VERSION, firecrackerArch, MICROVM_ARCHES, DEFAULT_BASE_IMAGE, GUEST_HELPER_SHA256, GUEST_ASSETS_BASE_URL, GUEST_KERNEL_SHA256, FIRECRACKER_BINARY_SHA256, FIRECRACKER_SHA256, FIRECRACKER_VERSION, GUEST_ASSETS_VERSION } from './chunk-CX22S5BA.js';
8
10
  import { execCapture } from './chunk-ZOAEPWGL.js';
9
11
  import { GUEST_CID } from './chunk-R7STSBLC.js';
10
- import { WorkCommandError, postJson } from './chunk-DGAIVMMN.js';
11
- import { parseDuKilobytes, hostAllowed } from './chunk-F6GFZXZ3.js';
12
- import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, checkEnvironment, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-OD2VTIHN.js';
13
- import { defaultGitRunner, materializeContext } from './chunk-5FWNEQNZ.js';
12
+ import { WorkCommandError, postJson } from './chunk-KV47VQJZ.js';
13
+ import { hostAllowed } from './chunk-UXYLWVTQ.js';
14
+ import { parseDuKilobytes } from './chunk-7RQ73DLO.js';
15
+ import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, checkEnvironment, AGENT_CREDENTIAL_MISSING } from './chunk-OD2VTIHN.js';
16
+ import { defaultGitRunner, materializeContext } from './chunk-BQFNZOMQ.js';
14
17
  import { readContextManifest, readSourceCache } from './chunk-ZZXLM64C.js';
15
- import { signingFetchFor } from './chunk-BCLJSQCV.js';
16
- import { signerFor } from './chunk-M5POMKKW.js';
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
- import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, realpathSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
18
+ import { signingFetchFor } from './chunk-YO4PSLVB.js';
19
+ import { signerFor } from './chunk-V7NIVOFD.js';
20
+ import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
21
+ import { relative, join, dirname, basename } from 'path';
22
+ import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
20
23
  import { spawnSync, spawn } from 'child_process';
21
- import { createHash, randomBytes } from 'crypto';
24
+ import { availableParallelism, networkInterfaces } from 'os';
25
+ import { randomBytes, createHash } from 'crypto';
22
26
  import { createServer, request } from 'http';
23
27
  import { request as request$1 } from 'https';
24
28
  import { Transform } from 'stream';
@@ -26,7 +30,6 @@ import { pipeline, finished } from 'stream/promises';
26
30
  import { createGunzip, gunzipSync, constants as constants$1 } from 'zlib';
27
31
  import { isIP, connect, createServer as createServer$1 } from 'net';
28
32
  import { lookup } from 'dns/promises';
29
- import { networkInterfaces } from 'os';
30
33
 
31
34
  createRequire(import.meta.url);
32
35
  function logStamp(at) {
@@ -86,7 +89,7 @@ function isRetryable(err) {
86
89
  }
87
90
  async function retrySend(send, deps = {}) {
88
91
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
89
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
92
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
90
93
  const windowMs = deps.windowMs ?? RETRY_WINDOW_MS;
91
94
  const startedMs = now().getTime();
92
95
  let attempts = 0;
@@ -466,118 +469,62 @@ function lifecycleSteps(lifecycle) {
466
469
  function lifecycleFailureReason(step, exitCode) {
467
470
  return `lifecycle command ${step.name} (${step.source}) exited ${exitCode}`;
468
471
  }
469
- var BUILD_LABEL = "ai.outerlayer.build";
470
- var RUNNER_LABEL = "ai.outerlayer.runner";
471
- function resolvedRunnerDir(configPath) {
472
+ function parseCpuList(text) {
473
+ const cores = /* @__PURE__ */ new Set();
474
+ for (const part of text.trim().split(",")) {
475
+ const match = /^(\d+)(?:-(\d+))?$/.exec(part.trim());
476
+ if (match === null) return void 0;
477
+ const low = Number(match[1]);
478
+ const high = match[2] === void 0 ? low : Number(match[2]);
479
+ if (high < low) return void 0;
480
+ for (let core = low; core <= high; core++) cores.add(core);
481
+ }
482
+ return [...cores].sort((a, b) => a - b);
483
+ }
484
+ function formatCpuList(cores) {
485
+ const sorted = [...new Set(cores)].sort((a, b) => a - b);
486
+ const parts = [];
487
+ for (let i = 0; i < sorted.length; i++) {
488
+ const start = sorted[i];
489
+ while (sorted[i + 1] === sorted[i] + 1) i++;
490
+ parts.push(start === sorted[i] ? String(start) : `${start}-${sorted[i]}`);
491
+ }
492
+ return parts.join(",");
493
+ }
494
+ function usableCpus(deps = {}) {
472
495
  try {
473
- return realpathSync(runnerDir(configPath));
496
+ const status = (deps.readStatus ?? (() => readFileSync("/proc/self/status", "utf8")))();
497
+ const cores = parseCpuList(/^Cpus_allowed_list:\s*(.+)$/m.exec(status)?.[1] ?? "");
498
+ if (cores !== void 0) return cores;
474
499
  } catch {
475
- try {
476
- return join(realpathSync(dirname(resolve(configPath))), "runner");
477
- } catch {
478
- return resolve(runnerDir(configPath));
479
- }
480
500
  }
501
+ return Array.from({ length: Math.max(1, (deps.parallelism ?? availableParallelism)()) }, (_unused, core) => core);
481
502
  }
482
- function runnerLabelValue(configPath) {
483
- return createHash("sha256").update(resolvedRunnerDir(configPath)).digest("hex").slice(0, 12);
484
- }
485
- function hardened(input) {
486
- const { memory, cpus, pids } = input.limits;
487
- return [
488
- "--label",
489
- `${BUILD_LABEL}=${input.buildId}`,
490
- "--label",
491
- `${RUNNER_LABEL}=${input.runnerId}`,
492
- "--network",
493
- "none",
494
- "--cap-drop",
495
- "ALL",
496
- "--security-opt",
497
- "no-new-privileges",
498
- "--read-only",
499
- "--user",
500
- input.user,
501
- // The same figure for swap as for memory leaves the container none.
502
- "--memory",
503
- memory,
504
- "--memory-swap",
505
- memory,
506
- "--cpus",
507
- String(cpus),
508
- "--pids-limit",
509
- String(pids)
510
- ];
511
- }
512
- function mountPath(path) {
513
- 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`);
514
- return path;
515
- }
516
- function containerRunArgs(input) {
517
- return [
518
- "--detach",
519
- "--name",
520
- input.container,
521
- ...hardened(input),
522
- "--mount",
523
- `type=volume,src=${input.volume},dst=/build`,
524
- // `/tmp` is a directory of the same volume, so it is measured and limited with the rest of the build.
525
- "--mount",
526
- `type=volume,src=${input.volume},dst=/tmp,volume-subpath=tmp`,
527
- "--mount",
528
- `type=bind,src=${mountPath(input.buildDir)},dst=${JOB_MOUNT},readonly`,
529
- "--entrypoint",
530
- "/opt/outerlayer/bin/tini",
531
- input.image,
532
- "--",
533
- "/opt/outerlayer/bin/node",
534
- "/opt/outerlayer/cli/dist/index.js",
535
- "broker-relay",
536
- "--port",
537
- String(RELAY_PORT),
538
- "--socket",
539
- `${JOB_MOUNT}/tunnel.sock`,
540
- ...Array.from({ length: input.destinations ?? 0 }, (_unused, index) => [
541
- "--forward",
542
- `${DESTINATION_BASE_PORT + index}=${JOB_MOUNT}/dest-${index}.sock`
543
- ]).flat(),
544
- ...input.builtIns === true ? [
545
- "--forward",
546
- `${GATEWAY_PORT}=${JOB_MOUNT}/${GATEWAY_SOCKET}`,
547
- "--forward",
548
- `${CLAUDE_PORT}=${JOB_MOUNT}/${CLAUDE_SOCKET}`
549
- ] : []
550
- ];
551
- }
552
- function prepareRunArgs(input) {
553
- return [
554
- "--rm",
555
- ...hardened(input),
556
- "--mount",
557
- `type=volume,src=${input.volume},dst=/build`,
558
- "--entrypoint",
559
- "mkdir",
560
- input.image,
561
- "-p",
562
- "-m",
563
- "1777",
564
- "/build/tmp",
565
- "/build/home",
566
- "/build/work"
567
- ];
568
- }
569
- function containerExecArgs(input) {
570
- const literals = Object.keys(input.env.literals).sort().flatMap((name) => ["-e", `${name}=${input.env.literals[name]}`]);
571
- const passed = Object.keys(input.env.passed).filter((name) => input.env.passed[name] !== void 0).sort().flatMap((name) => ["-e", name]);
572
- return [
573
- "-u",
574
- input.user,
575
- ...input.workdir === void 0 ? [] : ["-w", input.workdir],
576
- ...literals,
577
- ...passed,
578
- input.container,
579
- ...input.argv
580
- ];
503
+ function createCpuAllocator(cores) {
504
+ const usable = [...new Set(cores)].sort((a, b) => a - b);
505
+ const held = /* @__PURE__ */ new Map();
506
+ const holders = (core) => {
507
+ let count = 0;
508
+ for (const set of held.values()) if (set.includes(core)) count++;
509
+ return count;
510
+ };
511
+ return {
512
+ acquire(buildId, cpus) {
513
+ const existing = held.get(buildId);
514
+ if (existing !== void 0) return existing;
515
+ const wanted = Math.min(Math.ceil(cpus), usable.length);
516
+ const ranked = usable.map((core) => ({ core, count: holders(core) })).sort((a, b) => a.count - b.count);
517
+ const chosen = ranked.slice(0, wanted).map(({ core }) => core).sort((a, b) => a - b);
518
+ held.set(buildId, chosen);
519
+ return chosen;
520
+ },
521
+ hold(buildId, set) {
522
+ held.set(buildId, [...set]);
523
+ },
524
+ release(buildId) {
525
+ held.delete(buildId);
526
+ }
527
+ };
581
528
  }
582
529
  var AssetError = class extends Error {
583
530
  constructor(file, message) {
@@ -693,7 +640,7 @@ function templateScript() {
693
640
  ].join("\n");
694
641
  }
695
642
  var ids = () => ({ uid: String(process.getuid?.() ?? 0), gid: String(process.getgid?.() ?? 0) });
696
- function converterArgs(dest, extra, script) {
643
+ function converterArgs(dest, memoryBytes, extra, script) {
697
644
  const { uid, gid } = ids();
698
645
  return [
699
646
  "run",
@@ -701,6 +648,10 @@ function converterArgs(dest, extra, script) {
701
648
  "-i",
702
649
  "--network",
703
650
  "none",
651
+ "--memory",
652
+ String(memoryBytes),
653
+ "--memory-swap",
654
+ String(memoryBytes),
704
655
  "--mount",
705
656
  `type=bind,src=${dirname(dest)},dst=/out`,
706
657
  "-e",
@@ -719,7 +670,7 @@ function converterArgs(dest, extra, script) {
719
670
  }
720
671
  var firstLine2 = (text) => text.trim().split("\n")[0] ?? "";
721
672
  function exportInto(container, converter) {
722
- return new Promise((resolve2, reject) => {
673
+ return new Promise((resolve, reject) => {
723
674
  const exporter = spawn("docker", ["export", container], { stdio: ["ignore", "pipe", "pipe"], env: childEnvironment(process.env) });
724
675
  const writer = spawn("docker", converter, { stdio: ["pipe", "ignore", "pipe"], env: childEnvironment(process.env) });
725
676
  const errors = [];
@@ -736,7 +687,7 @@ function exportInto(container, converter) {
736
687
  if (code !== 0 && failure === void 0) failure = `${name} exited ${code}`;
737
688
  exits += 1;
738
689
  if (exits < 2) return;
739
- if (failure === void 0) resolve2();
690
+ if (failure === void 0) resolve();
740
691
  else reject(new Error(`${failure}: ${firstLine2(errors.join("\n"))}`));
741
692
  };
742
693
  exporter.once("exit", (code) => done("docker export", code));
@@ -746,18 +697,18 @@ function exportInto(container, converter) {
746
697
  function createRootfsHost() {
747
698
  const env = () => childEnvironment(process.env);
748
699
  return {
749
- async convert({ imageId, dest }) {
700
+ async convert({ imageId, dest, memoryBytes }) {
750
701
  const created = await spawnRun("docker", ["create", imageId, "none"], { env: env() });
751
702
  if (created.status !== 0) throw new Error(`docker create ${imageId} failed: ${firstLine2(created.stderr)}`);
752
703
  const container = created.stdout.trim();
753
704
  try {
754
- await exportInto(container, converterArgs(dest, [], converterScript()));
705
+ await exportInto(container, converterArgs(dest, memoryBytes, [], converterScript()));
755
706
  } finally {
756
707
  await spawnRun("docker", ["rm", "--force", "--volumes", container], { env: env() });
757
708
  }
758
709
  },
759
- async makeTemplate({ bytes, dest }) {
760
- const made = await spawnRun("docker", converterArgs(dest, ["-e", `BYTES=${bytes}`], templateScript()), { env: env() });
710
+ async makeTemplate({ bytes, dest, memoryBytes }) {
711
+ const made = await spawnRun("docker", converterArgs(dest, memoryBytes, ["-e", `BYTES=${bytes}`], templateScript()), { env: env() });
761
712
  if (made.status !== 0) throw new Error(`making the empty build disk failed: ${firstLine2(made.stderr)}`);
762
713
  },
763
714
  async imageConfig(imageId) {
@@ -806,14 +757,14 @@ async function ensureRootfs(input) {
806
757
  const path = rootfsPathFor(input.runnerDir, input.imageId);
807
758
  if (existsSync(path)) return path;
808
759
  const { uid, gid } = ids();
809
- await madeAtomically(path, (temp) => input.host.convert({ imageId: input.imageId, dest: temp, uid: Number(uid), gid: Number(gid) }));
760
+ await madeAtomically(path, (temp) => input.host.convert({ imageId: input.imageId, dest: temp, uid: Number(uid), gid: Number(gid), memoryBytes: input.memoryBytes }));
810
761
  return path;
811
762
  }
812
763
  async function createBuildDisk(input) {
813
764
  const template = join(diskTemplatesDir(input.runnerDir), `${input.bytes}.ext4`);
814
765
  if (!existsSync(template)) {
815
766
  const { uid, gid } = ids();
816
- await madeAtomically(template, (temp) => input.host.makeTemplate({ bytes: input.bytes, dest: temp, uid: Number(uid), gid: Number(gid) }));
767
+ await madeAtomically(template, (temp) => input.host.makeTemplate({ bytes: input.bytes, dest: temp, uid: Number(uid), gid: Number(gid), memoryBytes: input.memoryBytes }));
817
768
  }
818
769
  mkdirSync(dirname(input.dest), { recursive: true });
819
770
  if (input.host.copyTemplate) input.host.copyTemplate(template, input.dest);
@@ -885,7 +836,7 @@ function createFirecrackerHost(options = {}) {
885
836
  const failed = new Promise((_resolve, reject) => child.once("error", reject));
886
837
  const pid = child.pid;
887
838
  if (pid === void 0) return await failed;
888
- exits.set(pid, new Promise((resolve2) => child.once("exit", () => resolve2())));
839
+ exits.set(pid, new Promise((resolve) => child.once("exit", () => resolve())));
889
840
  child.unref();
890
841
  const start = startTimeOf(pid);
891
842
  if (start === null) throw new Error(`${command} exited as soon as it started; its console is at ${input.consolePath}`);
@@ -902,8 +853,8 @@ function createFirecrackerHost(options = {}) {
902
853
  }
903
854
  const exited = exits.get(vm.pid);
904
855
  const deadline = Date.now() + STOP_WAIT_MS;
905
- if (exited !== void 0) await Promise.race([exited, new Promise((resolve2) => setTimeout(resolve2, STOP_WAIT_MS))]);
906
- while (alive(vm) && Date.now() < deadline) await new Promise((resolve2) => setTimeout(resolve2, POLL_MS));
856
+ if (exited !== void 0) await Promise.race([exited, new Promise((resolve) => setTimeout(resolve, STOP_WAIT_MS))]);
857
+ while (alive(vm) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, POLL_MS));
907
858
  exits.delete(vm.pid);
908
859
  },
909
860
  alive
@@ -913,7 +864,7 @@ function createFirecrackerHost(options = {}) {
913
864
  // src/runner/microvm/host.ts
914
865
  function createMicroVmPorts(over = {}) {
915
866
  return {
916
- assets: over.assets ?? ((runnerDir2) => ensureAssets({ runnerDir: runnerDir2 })),
867
+ assets: over.assets ?? ((runnerDir) => ensureAssets({ runnerDir })),
917
868
  rootfs: over.rootfs ?? createRootfsHost(),
918
869
  firecracker: over.firecracker ?? createFirecrackerHost(),
919
870
  cgroup: over.cgroup ?? realCgroupPort(),
@@ -926,6 +877,10 @@ var aborted = () => ({ ok: false, reason: "", aborted: true });
926
877
  function firstLine3(text) {
927
878
  return text.trim().split("\n")[0] ?? "";
928
879
  }
880
+ function gitErrorLine(text) {
881
+ const lines = text.split("\n").map((line) => line.trim()).filter((line) => line !== "");
882
+ return lines.find((line) => /^(fatal|error):/.test(line)) ?? lines.at(-1) ?? "";
883
+ }
929
884
  function adoptionFailureReason(result) {
930
885
  const line = result.stdout.trim().split("\n").at(-1) ?? "";
931
886
  try {
@@ -940,7 +895,10 @@ async function runBuildSteps(input) {
940
895
  const rewrite = await exec(["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"], "/build/work");
941
896
  if (rewrite.status !== 0) return { ok: false, reason: `configuring the build's git remote failed (exit ${rewrite.status})` };
942
897
  const clone = await exec(["git", "clone", "--filter=blob:none", "--single-branch", `https://github.com/${repository}.git`, CHECKOUT_DIR], "/build/work");
943
- if (clone.status !== 0) return { ok: false, reason: `the clone of ${repository} failed (exit ${clone.status})` };
898
+ if (clone.status !== 0) {
899
+ const detail = gitErrorLine(clone.stderr);
900
+ return { ok: false, reason: `the clone of ${repository} failed (exit ${clone.status})${detail === "" ? "" : `: ${detail}`}` };
901
+ }
944
902
  try {
945
903
  const prepared = await prepareBranch({
946
904
  workdir: CHECKOUT_DIR,
@@ -1155,9 +1113,9 @@ async function git(args, cwd, env, input) {
1155
1113
  if (size < maxOutputBytes) out.push(chunk);
1156
1114
  size += chunk.length;
1157
1115
  });
1158
- const closed = new Promise((resolve2, reject) => {
1116
+ const closed = new Promise((resolve, reject) => {
1159
1117
  child.once("error", reject);
1160
- child.once("close", (status) => resolve2(status));
1118
+ child.once("close", (status) => resolve(status));
1161
1119
  });
1162
1120
  if (input !== void 0) {
1163
1121
  await pipeline(input, child.stdin).catch(() => void 0);
@@ -1354,14 +1312,14 @@ async function drain(req) {
1354
1312
  }
1355
1313
  }
1356
1314
  function readPrefix(req, gzip) {
1357
- return new Promise((resolve2, reject) => {
1315
+ return new Promise((resolve, reject) => {
1358
1316
  const held = [];
1359
1317
  const finish = (section) => {
1360
1318
  req.off("data", onData);
1361
1319
  req.off("end", onEnd);
1362
1320
  req.off("error", reject);
1363
1321
  req.pause();
1364
- resolve2({ held, section });
1322
+ resolve({ held, section });
1365
1323
  };
1366
1324
  const onData = (chunk) => {
1367
1325
  held.push(chunk);
@@ -1375,19 +1333,19 @@ function readPrefix(req, gzip) {
1375
1333
  });
1376
1334
  }
1377
1335
  function readBody(req, limit) {
1378
- return new Promise((resolve2, reject) => {
1336
+ return new Promise((resolve, reject) => {
1379
1337
  const chunks = [];
1380
1338
  let size = 0;
1381
1339
  req.on("data", (chunk) => {
1382
1340
  size += chunk.length;
1383
1341
  if (size > limit) {
1384
1342
  req.pause();
1385
- resolve2(void 0);
1343
+ resolve(void 0);
1386
1344
  return;
1387
1345
  }
1388
1346
  chunks.push(chunk);
1389
1347
  });
1390
- req.once("end", () => resolve2(Buffer.concat(chunks)));
1348
+ req.once("end", () => resolve(Buffer.concat(chunks)));
1391
1349
  req.once("error", reject);
1392
1350
  });
1393
1351
  }
@@ -1400,7 +1358,7 @@ function createGitRemote(options) {
1400
1358
  const { route, res } = input;
1401
1359
  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}`}`);
1402
1360
  const send = url.protocol === "https:" ? request$1 : request;
1403
- return new Promise((resolve2) => {
1361
+ return new Promise((resolve) => {
1404
1362
  const upstream = send(url, { method: input.method, headers: { ...input.headers, connection: "close" } }, (answer) => {
1405
1363
  const headers = {};
1406
1364
  for (const [name, value] of Object.entries(answer.headers)) {
@@ -1422,18 +1380,18 @@ function createGitRemote(options) {
1422
1380
  });
1423
1381
  answer.once("end", () => {
1424
1382
  res.end();
1425
- resolve2();
1383
+ resolve();
1426
1384
  });
1427
1385
  answer.once("error", () => {
1428
1386
  res.destroy();
1429
- resolve2();
1387
+ resolve();
1430
1388
  });
1431
1389
  });
1432
1390
  upstream.on("error", (err) => {
1433
1391
  log(`could not reach GitHub for ${route.kind}: ${err.message}`);
1434
1392
  if (res.headersSent) res.destroy();
1435
1393
  else plain(res, 502, "the runner could not reach GitHub\n");
1436
- resolve2();
1394
+ resolve();
1437
1395
  });
1438
1396
  res.once("close", () => upstream.destroy());
1439
1397
  input.feed(upstream);
@@ -1728,7 +1686,7 @@ async function startTunnel(socketPath, opts = {}) {
1728
1686
  if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH_BYTES) {
1729
1687
  throw new Error(`${socketPath} is too long for a Unix socket; keep the runner's config file nearer the root of the filesystem`);
1730
1688
  }
1731
- const resolve2 = opts.resolve ?? systemResolve;
1689
+ const resolve = opts.resolve ?? systemResolve;
1732
1690
  const openConnection = opts.connect ?? ((t) => connect({ host: t.address, port: t.port }));
1733
1691
  const ownAddresses = opts.hostAddresses ?? systemHostAddresses;
1734
1692
  const connectTimeoutMs = opts.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
@@ -1812,7 +1770,7 @@ async function startTunnel(socketPath, opts = {}) {
1812
1770
  }
1813
1771
  let addresses;
1814
1772
  try {
1815
- addresses = await resolve2(host);
1773
+ addresses = await resolve(host);
1816
1774
  } catch {
1817
1775
  return { ok: false, status: 502, body: `could not resolve ${host}
1818
1776
  ` };
@@ -1983,6 +1941,7 @@ function createContainerRuntime(opts = {}) {
1983
1941
  const { cli, remoteFor } = opts;
1984
1942
  return {
1985
1943
  host: opts.host ?? createContainerHost(),
1944
+ cpus: opts.cpus ?? createCpuAllocator(usableCpus()),
1986
1945
  tunnels: opts.tunnels ?? /* @__PURE__ */ new Map(),
1987
1946
  buildImage: opts.buildImage ?? ((build) => buildRepositoryImage(
1988
1947
  {
@@ -1991,13 +1950,14 @@ function createContainerRuntime(opts = {}) {
1991
1950
  configPath: build.configPath,
1992
1951
  cli: cli ?? currentCliBuild(),
1993
1952
  readToken: build.readToken,
1994
- guestHelper: build.guestHelper
1953
+ guestHelper: build.guestHelper,
1954
+ log: build.log
1995
1955
  },
1996
- { host: opts.imageHost ?? createDockerHost() }
1956
+ { host: opts.imageHost ?? createDockerHost({ builder: builderSpec(build.configPath, build.buildMemory, opts.hostEnv ?? process.env) }) }
1997
1957
  )),
1998
1958
  startTunnel: opts.startTunnel ?? startTunnel,
1999
1959
  tunnelOptions: opts.tunnelOptions,
2000
- sleep: opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms))),
1960
+ sleep: opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
2001
1961
  hostEnv: opts.hostEnv ?? process.env,
2002
1962
  credentials: opts.credentials ?? /* @__PURE__ */ new Map(),
2003
1963
  github: { gitUrl: opts.github?.gitUrl ?? "https://github.com", apiUrl: opts.github?.apiUrl ?? "https://api.github.com" },
@@ -2068,7 +2028,17 @@ async function obtainImage(input) {
2068
2028
  };
2069
2029
  }
2070
2030
  try {
2071
- return { ok: true, built: await runtime.buildImage({ repository, configPath: config.configPath, readToken, guestHelper: input.guestHelper }) };
2031
+ return {
2032
+ ok: true,
2033
+ built: await runtime.buildImage({
2034
+ repository,
2035
+ configPath: config.configPath,
2036
+ buildMemory: config.build?.memory ?? "8g",
2037
+ readToken,
2038
+ guestHelper: input.guestHelper,
2039
+ log: (line) => log.line(`${tag} ${line}`)
2040
+ })
2041
+ };
2072
2042
  } catch (err) {
2073
2043
  if (err instanceof RecipeError && err.code === "build_in_progress" && (input.now?.() ?? /* @__PURE__ */ new Date()).getTime() < input.deadlineMs) {
2074
2044
  log.line(`${tag} waiting for another build of ${repository}'s image`);
@@ -2077,7 +2047,8 @@ async function obtainImage(input) {
2077
2047
  }
2078
2048
  if (err instanceof RecipeError) {
2079
2049
  log.error(`${tag} image build for ${repository} failed: ${err.code}: ${err.message}`);
2080
- return { ok: false, reason: err.code };
2050
+ const says = err.code === "image_build_out_of_memory" || err.code === "image_builder_unavailable";
2051
+ return { ok: false, reason: says ? err.message : err.code };
2081
2052
  }
2082
2053
  return { ok: false, reason: `the image build failed: ${describeCallFailure(err)}` };
2083
2054
  }
@@ -2118,7 +2089,8 @@ async function provisionContainer(input) {
2118
2089
  imageId: report.imageId,
2119
2090
  containerEnv: report.apply.containerEnv,
2120
2091
  builtIns: true,
2121
- lifecycle: report.apply.lifecycle
2092
+ lifecycle: report.apply.lifecycle,
2093
+ cpuset: formatCpuList(runtime.cpus.acquire(buildId, config.build?.cpus ?? 4))
2122
2094
  };
2123
2095
  const env = buildEnvironment({
2124
2096
  config,
@@ -2170,7 +2142,7 @@ async function provisionContainer(input) {
2170
2142
  } catch (err) {
2171
2143
  return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
2172
2144
  }
2173
- const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
2145
+ const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096, cpuset: state.cpuset };
2174
2146
  const runnerId = runnerLabelValue(config.configPath);
2175
2147
  const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
2176
2148
  if (volume.status !== 0) return dockerFailed("creating the build's volume", volume);
@@ -2401,6 +2373,7 @@ async function cleanupContainer(runtime, state, jobDir) {
2401
2373
  await tunnel.close();
2402
2374
  }
2403
2375
  rmSync(state.buildDir, { recursive: true, force: true });
2376
+ runtime.cpus.release(state.buildId);
2404
2377
  if (failures.length > 0) throw new Error(failures.join("; "));
2405
2378
  }
2406
2379
  async function startCheckContainer(input) {
@@ -2416,7 +2389,8 @@ async function startCheckContainer(input) {
2416
2389
  const name = `outerlayer-check-${buildId}`;
2417
2390
  const buildDir2 = buildDir(config.configPath, buildId);
2418
2391
  const agentUser = built.agentUser ?? ADDED_USER;
2419
- const state = { buildId, container: name, volume: name, buildDir: buildDir2, repository, imageId: built.imageId, agentUser };
2392
+ const cpuset = built.cpuset ?? formatCpuList(runtime.cpus.acquire(buildId, config.build?.cpus ?? 4));
2393
+ const state = { buildId, container: name, volume: name, buildDir: buildDir2, repository, imageId: built.imageId, agentUser, cpuset };
2420
2394
  const checksDir = join(jobDir, "checks");
2421
2395
  mkdirSync(checksDir, { recursive: true });
2422
2396
  const logPath = join(checksDir, "environment.log");
@@ -2438,7 +2412,7 @@ async function startCheckContainer(input) {
2438
2412
  } catch (err) {
2439
2413
  return fail(`the tunnel could not start: ${describeCallFailure(err)}`);
2440
2414
  }
2441
- const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096 };
2415
+ const limits2 = { memory: config.build?.memory ?? "8g", cpus: config.build?.cpus ?? 4, pids: config.build?.pids ?? 4096, cpuset };
2442
2416
  const runnerId = runnerLabelValue(config.configPath);
2443
2417
  const volume = await host.createVolume(name, { [BUILD_LABEL]: buildId, [RUNNER_LABEL]: runnerId });
2444
2418
  if (volume.status !== 0) return dockerFailed("creating the check environment's volume", volume);
@@ -2474,4 +2448,4 @@ async function startCheckContainer(input) {
2474
2448
  };
2475
2449
  }
2476
2450
 
2477
- export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, outOfMemoryReason, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startCheckContainer, startServicesFor, stopContainer, syncInContainer };
2451
+ export { ADDED_USER, AgentUserError, AssetError, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, outOfMemoryReason, parseCpuList, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startCheckContainer, startServicesFor, stopContainer, syncInContainer };