@outerlayer/cli 0.5.1 → 0.6.0

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 (65) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +9 -0
  3. package/dist/{agent-setup-IFH3FB5X.js → agent-setup-USZ2SAXC.js} +2 -2
  4. package/dist/build-info.json +1 -1
  5. package/dist/{check-4Q33B5ZE.js → check-CCVPBN7A.js} +24 -14
  6. package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
  7. package/dist/{chunk-F4NU4H3V.js → chunk-43J3M6KB.js} +95 -11
  8. package/dist/{chunk-W7UTFZDH.js → chunk-4NLKY4WR.js} +2 -2
  9. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  10. package/dist/{chunk-D2KWEAN6.js → chunk-7DBI6SNT.js} +1 -1
  11. package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
  12. package/dist/{chunk-EX6M5JPR.js → chunk-ANK2B36Y.js} +131 -15
  13. package/dist/{chunk-RMAZ2NWA.js → chunk-BNMCXPG7.js} +14 -2
  14. package/dist/{chunk-UFFXNXLQ.js → chunk-FA2BOX4O.js} +31 -29
  15. package/dist/chunk-FBTE2A2X.js +38 -0
  16. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  17. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  18. package/dist/{chunk-JLDZTG3A.js → chunk-IASWYRVG.js} +1 -1
  19. package/dist/{install-cmd-7WXOIFDO.js → chunk-JWOQNNN2.js} +0 -1
  20. package/dist/{chunk-H4CK2YK4.js → chunk-MIGEDH5P.js} +16 -8
  21. package/dist/{chunk-CHLVJNTE.js → chunk-MXB5VB4Y.js} +17 -5
  22. package/dist/chunk-NQXFAGHU.js +211 -0
  23. package/dist/chunk-OUUINEYB.js +27 -0
  24. package/dist/{chunk-OR6AEEDO.js → chunk-PMAMOSTP.js} +3 -3
  25. package/dist/{chunk-RTXKYLCA.js → chunk-RBOMWNOG.js} +1 -1
  26. package/dist/{chunk-T52PKE2Y.js → chunk-RZSF5B46.js} +149 -7
  27. package/dist/chunk-T6OIJQRT.js +31 -0
  28. package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
  29. package/dist/{chunk-UWH5DSUP.js → chunk-VMJ3MMBX.js} +203 -80
  30. package/dist/chunk-ZILGTTY6.js +75 -0
  31. package/dist/{cli-LHSUYYMI.js → cli-7WCNPBYC.js} +134 -61
  32. package/dist/command-ARFK3OYF.js +162 -0
  33. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-PCESZLTR.js} +4 -4
  34. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-G5JAELE7.js} +2 -2
  35. package/dist/{context-materialize-G2FUFC72.js → context-materialize-3HLPZAF6.js} +2 -2
  36. package/dist/{dist-WDV7U7UJ.js → dist-RDLP4GGJ.js} +1 -1
  37. package/dist/{doctor-MOA3RIEP.js → doctor-VOMYOHQU.js} +32 -12
  38. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-TBF46VXZ.js} +6 -7
  39. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-3427XKFW.js} +3 -3
  40. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-GSI75K36.js} +3 -3
  41. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-3PRQKB55.js} +6 -7
  42. package/dist/{hook-fast-EYENPK6F.js → hook-fast-NJBBZQ5Q.js} +1 -1
  43. package/dist/index.js +3 -2
  44. package/dist/{init-2IPOIXJV.js → init-OHYFIKZD.js} +26 -55
  45. package/dist/{init-FYDJAKTM.js → init-WVR4BZQ4.js} +8 -8
  46. package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-HMFNIFPI.js} +7 -7
  47. package/dist/inspect-XAUSB7L3.js +63 -0
  48. package/dist/install-DSUZE2TJ.js +13 -0
  49. package/dist/install-cmd-453C7IZT.js +8 -0
  50. package/dist/lima-7NPCNGWT.js +6 -0
  51. package/dist/{loop-NEBUGD2Q.js → loop-UAJB34KA.js} +24 -11
  52. package/dist/{machine-ECU3QCZD.js → machine-2JOWGRSZ.js} +7 -7
  53. package/dist/session-sender-ED26V45B.js +28 -0
  54. package/dist/{status-CLATHCTQ.js → status-6WNXX2O7.js} +22 -8
  55. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-YONVGUYS.js} +6 -7
  56. package/dist/{watch-3YW6Z3DQ.js → watch-HHKAF26L.js} +6 -7
  57. package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-LXBZTMJA.js} +5 -5
  58. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-OVNF44FY.js} +3 -3
  59. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-6DGQNTAH.js} +3 -3
  60. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  61. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-FCZHFHZS.js} +4 -4
  62. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-QJJVHZDC.js} +2 -2
  63. package/package.json +2 -1
  64. package/dist/chunk-ZOC7JSYC.js +0 -83
  65. package/dist/{chunk-XDDW4FRS.js → chunk-OIRNH6UO.js} +1 -1
@@ -1,27 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
+ import { writeJobFile, readJobFile } from './chunk-T6OIJQRT.js';
4
+ import { senderPidfile } from './chunk-ZILGTTY6.js';
3
5
  import { DOCKERD_LOG } from './chunk-CBPPJSAR.js';
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-T52PKE2Y.js';
6
+ import { installScrubContext } from './chunk-UMFWVGJO.js';
7
+ import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, senderSignalScript } from './chunk-RZSF5B46.js';
6
8
  import { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
7
9
  import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
8
10
  import { childEnvironment } from './chunk-774HEQL6.js';
9
11
  import { waitForAgent, vmExecArgs } from './chunk-W4SQCOZU.js';
10
12
  import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-B2G7JHHB.js';
11
- import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-RMAZ2NWA.js';
12
- import { planArtifactUpload } from './chunk-WOBNRVQT.js';
13
- import { scrubText } from './chunk-EX6M5JPR.js';
13
+ import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-BNMCXPG7.js';
14
+ import { planArtifactUpload } from './chunk-3DEVYTNQ.js';
15
+ import { scrubText } from './chunk-ANK2B36Y.js';
14
16
  import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
15
17
  import { parseSize } from './chunk-F6GFZXZ3.js';
16
18
  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
19
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
18
20
  import { resolveVersion } from './chunk-RA3O54FC.js';
19
21
  import { signerFor } from './chunk-M5POMKKW.js';
20
- import { runnerDir, buildDir, vsockPath, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
22
+ import { runnerDir, buildDir, vsockPath, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
21
23
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
22
24
  import { spawn, spawnSync } from 'child_process';
23
25
  import { randomBytes } from 'crypto';
24
- import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, existsSync, readFileSync, openSync, closeSync, rmSync, readdirSync } from 'fs';
26
+ import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync } from 'fs';
25
27
  import { join, isAbsolute, dirname, delimiter } from 'path';
26
28
 
27
29
  createRequire(import.meta.url);
@@ -192,9 +194,15 @@ async function provisionMicroVm(input) {
192
194
  return bail(`the build's disk and machine could not be made: ${describeCallFailure(err)}`);
193
195
  }
194
196
  if (input.aborted()) return bail("", true);
197
+ try {
198
+ state.cgroup = vm.cgroup.create(buildId, limits.memoryBytes);
199
+ } catch (err) {
200
+ return bail(`the build's memory limit could not be set: ${describeCallFailure(err)}`);
201
+ }
202
+ if (state.cgroup !== void 0) input.save(state, environment);
195
203
  const consolePath = join(jobDir, "vm-console.log");
196
204
  try {
197
- state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath });
205
+ state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath, cgroup: state.cgroup });
198
206
  } catch (err) {
199
207
  return bail(`Firecracker did not start: ${describeCallFailure(err)}`);
200
208
  }
@@ -315,7 +323,7 @@ function agentVersionOf(toolsJson) {
315
323
  }
316
324
  return CLAUDE_CODE_VERSION;
317
325
  }
318
- function microVmCommandLine(input) {
326
+ function vmExecFor(input, argv) {
319
327
  const state = input.job.microvm;
320
328
  const env = buildEnvironment({
321
329
  config: input.config,
@@ -334,13 +342,17 @@ function microVmCommandLine(input) {
334
342
  cwd: CHECKOUT_DIR,
335
343
  env: { ...state.imageEnv, ...env.literals },
336
344
  pass: Object.keys(env.passed).filter((name) => env.passed[name] !== void 0),
337
- argv: ["/bin/sh", "-c", input.cmdline]
338
- };
339
- return {
340
- line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "),
341
- options,
342
- env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
345
+ argv
343
346
  };
347
+ return { options, env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
348
+ }
349
+ function microVmCommandLine(input) {
350
+ const { options, env } = vmExecFor(input, ["/bin/sh", "-c", input.cmdline]);
351
+ return { line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "), options, env };
352
+ }
353
+ function microVmSenderCommand(input) {
354
+ const { options, env } = vmExecFor(input, ["outerlayer", "session-sender"]);
355
+ return { command: process.execPath, args: [input.cli, "vm-exec", ...vmExecArgs(options)], env };
344
356
  }
345
357
  function isRunning(runtime, state) {
346
358
  return state.firecracker !== void 0 && runtime.vm.firecracker.alive(state.firecracker);
@@ -391,6 +403,13 @@ async function cleanupMicroVm(runtime, state, jobDir) {
391
403
  failures.push(`the microVM (pid ${state.firecracker.pid}) was not stopped: ${describeCallFailure(err)}`);
392
404
  }
393
405
  }
406
+ if (state.cgroup !== void 0) {
407
+ try {
408
+ runtime.vm.cgroup.remove(state.cgroup);
409
+ } catch (err) {
410
+ failures.push(`the build's cgroup ${state.cgroup} was not removed: ${describeCallFailure(err)}`);
411
+ }
412
+ }
394
413
  if (state.buildDisk !== void 0) deleteBuildDisk(state.buildDisk);
395
414
  const tunnel = runtime.tunnels.get(state.buildId);
396
415
  if (tunnel !== void 0) {
@@ -583,70 +602,6 @@ async function closeHostBuild(runtime, build) {
583
602
  rmSync(build.buildDir, { recursive: true, force: true });
584
603
  if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
585
604
  }
586
- var ENDED = "ended before provision finished";
587
- async function provisionProcess(input) {
588
- const { config, runtime, item, jobDir } = input;
589
- if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
590
- const repository = item.specRepository;
591
- if (repository === void 0 || repository === null || repository === "") {
592
- return { ok: false, reason: "the work item names no repository to clone" };
593
- }
594
- const buildId = randomBytes(6).toString("hex");
595
- const buildDir2 = buildDir(config.configPath, buildId);
596
- const workDir = join(jobDir, "work");
597
- const state = { buildId, buildDir: buildDir2, workDir };
598
- const environment = sharedUserEnvironment(input.cliVersion);
599
- input.save(state, environment);
600
- const bail = async (reason) => {
601
- try {
602
- await cleanupProcess(runtime, state);
603
- } catch (err) {
604
- input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
605
- }
606
- return { ok: false, reason };
607
- };
608
- if (input.aborted()) return bail(ENDED);
609
- mkdirSync(buildDir2, { recursive: true });
610
- chmodSync(buildDir2, 493);
611
- try {
612
- await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
613
- } catch (err) {
614
- return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
615
- }
616
- input.save(state, environment);
617
- if (input.aborted()) return bail(ENDED);
618
- const checkout = join(workDir, "checkout");
619
- const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
620
- const cloned = await runtime.clone({ remote, dest: checkout });
621
- if (!cloned.ok) return bail(cloned.reason);
622
- if (input.aborted()) return bail(ENDED);
623
- return { ok: true, workdir: checkout, state, environment };
624
- }
625
- function cleanupProcess(runtime, state) {
626
- return closeHostBuild(runtime, state);
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
- }
650
605
 
651
606
  // src/runner/verdict.ts
652
607
  var VERDICT_SETTLE_MS = 5 * 6e4;
@@ -720,6 +675,48 @@ async function settleVerdict(deps) {
720
675
  await deps.sleep(everyMs);
721
676
  }
722
677
  }
678
+ var ENDED = "ended before provision finished";
679
+ async function provisionProcess(input) {
680
+ const { config, runtime, item, jobDir } = input;
681
+ if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
682
+ const repository = item.specRepository;
683
+ if (repository === void 0 || repository === null || repository === "") {
684
+ return { ok: false, reason: "the work item names no repository to clone" };
685
+ }
686
+ const buildId = randomBytes(6).toString("hex");
687
+ const buildDir2 = buildDir(config.configPath, buildId);
688
+ const workDir = join(jobDir, "work");
689
+ const state = { buildId, buildDir: buildDir2, workDir };
690
+ const environment = sharedUserEnvironment(input.cliVersion);
691
+ input.save(state, environment);
692
+ const bail = async (reason) => {
693
+ try {
694
+ await cleanupProcess(runtime, state);
695
+ } catch (err) {
696
+ input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
697
+ }
698
+ return { ok: false, reason };
699
+ };
700
+ if (input.aborted()) return bail(ENDED);
701
+ mkdirSync(buildDir2, { recursive: true });
702
+ chmodSync(buildDir2, 493);
703
+ try {
704
+ await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
705
+ } catch (err) {
706
+ return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
707
+ }
708
+ input.save(state, environment);
709
+ if (input.aborted()) return bail(ENDED);
710
+ const checkout = join(workDir, "checkout");
711
+ const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
712
+ const cloned = await runtime.clone({ remote, dest: checkout });
713
+ if (!cloned.ok) return bail(cloned.reason);
714
+ if (input.aborted()) return bail(ENDED);
715
+ return { ok: true, workdir: checkout, state, environment };
716
+ }
717
+ function cleanupProcess(runtime, state) {
718
+ return closeHostBuild(runtime, state);
719
+ }
723
720
 
724
721
  // src/runner/job.ts
725
722
  var LEASE_SECONDS = 900;
@@ -1167,6 +1164,11 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1167
1164
  const jobFilePath = join(jobDirPath, "job.json");
1168
1165
  const logPath = join(jobDirPath, "command.log");
1169
1166
  const tag = `#${job.item}`;
1167
+ if (job.sender !== void 0) {
1168
+ await stopSender({ job, deps: { spawnFn, runtime: deps.container, log } });
1169
+ delete job.sender;
1170
+ writeJobFile(jobFilePath, job);
1171
+ }
1170
1172
  const build = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.container ?? createContainerRuntime() };
1171
1173
  const vmBuild = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.container ?? createContainerRuntime() };
1172
1174
  if (build !== void 0 && job.pid !== void 0 && isGroupAlive(job.pid)) {
@@ -1189,6 +1191,20 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1189
1191
  job.reason = scrubReason(ending.reason, config);
1190
1192
  writeJobFile(jobFilePath, job);
1191
1193
  }
1194
+ let outOfMemory;
1195
+ if (vmBuild?.state.cgroup !== void 0 && outcome !== void 0 && outcome !== "ok" && ending?.reason === void 0) {
1196
+ try {
1197
+ if (vmBuild.runtime.vm.cgroup.oomKills(vmBuild.state.cgroup) > 0) {
1198
+ outOfMemory = `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${config.build?.memory ?? "8g"}) plus 256 MiB for Firecracker. Raise runner.build.memory, or lower runner.concurrency`;
1199
+ }
1200
+ } catch (err) {
1201
+ log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
1202
+ }
1203
+ if (outOfMemory !== void 0) {
1204
+ job.reason = scrubReason(outOfMemory, config);
1205
+ writeJobFile(jobFilePath, job);
1206
+ }
1207
+ }
1192
1208
  const hookLimits = () => hookLimitsFor(config, now().getTime(), deps.now);
1193
1209
  let tunnelRecord;
1194
1210
  if (!job.cleanupRan) {
@@ -1352,6 +1368,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1352
1368
  const fromHook = readReasonLine(jobDirPath, config);
1353
1369
  if (verdictReason !== void 0) {
1354
1370
  job.reason = verdictReason;
1371
+ } else if (outOfMemory !== void 0) {
1372
+ job.reason = scrubReason(outOfMemory, config);
1355
1373
  } else if (ending?.reason === void 0) {
1356
1374
  job.reason = (outcome === "ok" ? void 0 : held?.reason) ?? (isolated ? fromHook ?? job.reason : fromHook);
1357
1375
  }
@@ -1470,4 +1488,109 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
1470
1488
  return recovered;
1471
1489
  }
1472
1490
 
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 };
1491
+ // src/runner/sender.ts
1492
+ var STOP_WAIT_MS = 3e4;
1493
+ var POLL_MS = 200;
1494
+ var BUILD_HOME = "/build/home";
1495
+ async function startSender(input) {
1496
+ const { config, job, jobDir, deps } = input;
1497
+ const tag = `#${job.item}`;
1498
+ if (job.itemKey === void 0) return void 0;
1499
+ const spawnFn = deps.spawnFn ?? spawn;
1500
+ try {
1501
+ const launch = launchFor(config, job, jobDir, deps);
1502
+ const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
1503
+ let pid;
1504
+ try {
1505
+ const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
1506
+ child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
1507
+ child.on("exit", (code, signal) => deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`));
1508
+ child.unref?.();
1509
+ pid = child.pid;
1510
+ } finally {
1511
+ closeSync(fd);
1512
+ }
1513
+ if (pid === void 0) {
1514
+ deps.log.line(`${tag} session sender did not start: no pid`);
1515
+ return void 0;
1516
+ }
1517
+ deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
1518
+ return { pid, start: startTimeOf(pid) ?? void 0 };
1519
+ } catch (err) {
1520
+ deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
1521
+ return void 0;
1522
+ }
1523
+ }
1524
+ function launchFor(config, job, jobDir, deps) {
1525
+ if (job.microvm !== void 0) {
1526
+ const runtime = deps.runtime ?? createContainerRuntime();
1527
+ const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
1528
+ return { ...microVmSenderCommand({ job, runtime, config, cli }), cwd: jobDir };
1529
+ }
1530
+ if (job.container !== void 0) {
1531
+ return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
1532
+ }
1533
+ const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
1534
+ const build = job.build;
1535
+ const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
1536
+ if (build?.exec !== void 0) {
1537
+ return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
1538
+ }
1539
+ return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
1540
+ }
1541
+ function isLive(sender) {
1542
+ if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
1543
+ try {
1544
+ process.kill(sender.pid, 0);
1545
+ return true;
1546
+ } catch {
1547
+ return false;
1548
+ }
1549
+ }
1550
+ function signalHost(sender, signal) {
1551
+ if (!isLive(sender)) return;
1552
+ try {
1553
+ process.kill(-sender.pid, signal);
1554
+ } catch {
1555
+ try {
1556
+ process.kill(sender.pid, signal);
1557
+ } catch {
1558
+ }
1559
+ }
1560
+ }
1561
+ async function signalInGuest(runtime, state, pidfile, signal) {
1562
+ await runtime.vm.exec(state.vsockPath, {
1563
+ argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
1564
+ user: state.agentUser ?? ADDED_USER,
1565
+ cwd: "/",
1566
+ env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
1567
+ });
1568
+ }
1569
+ async function stopSender(input) {
1570
+ const { job, deps } = input;
1571
+ const sender = job.sender;
1572
+ if (sender === void 0) return;
1573
+ const tag = `#${job.item}`;
1574
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1575
+ const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1576
+ const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1577
+ const signalInside = async (signal) => {
1578
+ if (inContainer !== void 0) await signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
1579
+ if (inGuest !== void 0) await signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
1580
+ };
1581
+ try {
1582
+ if (inContainer !== void 0 || inGuest !== void 0) await signalInside("TERM");
1583
+ else signalHost(sender, "SIGTERM");
1584
+ const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
1585
+ for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) await sleep(POLL_MS);
1586
+ if (isLive(sender)) {
1587
+ deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
1588
+ await signalInside("KILL");
1589
+ signalHost(sender, "SIGKILL");
1590
+ }
1591
+ } catch (err) {
1592
+ deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
1593
+ }
1594
+ }
1595
+
1596
+ 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, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, startSender, stopOrphanedMicroVms, unfinishedJobs, workdirProblem };
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { LiveUploader } from './chunk-UMFWVGJO.js';
4
+ import { CaptureDaemon } from './chunk-ANK2B36Y.js';
5
+ import { parseWorkItemEnv } from './chunk-GQD22KKN.js';
6
+ import { spoolDir } from './chunk-FA2BOX4O.js';
7
+ import { homeFromEnv } from './chunk-VNVZDWO3.js';
8
+ import { mkdirSync, writeFileSync, rmSync } from 'fs';
9
+ import { join } from 'path';
10
+
11
+ createRequire(import.meta.url);
12
+ var SENDER_STOP_WAIT_MS = 25e3;
13
+ var RESCAN_MS = 3e4;
14
+ function senderPidfile(home, item) {
15
+ return join(spoolDir(home), `session-sender-${item}.pid`);
16
+ }
17
+ async function startSessionSender(opts = {}) {
18
+ const env = opts.env ?? process.env;
19
+ const stderr = opts.stderr ?? ((s) => void process.stderr.write(s));
20
+ const parsed = parseWorkItemEnv(env.OUTERLAYER_WORK);
21
+ const jobDir = env.OUTERLAYER_JOB_DIR;
22
+ if ("reason" in parsed || !jobDir) {
23
+ throw new Error("a session sender runs inside a runner job: OUTERLAYER_WORK must name the item and OUTERLAYER_JOB_DIR the job");
24
+ }
25
+ const home = opts.home ?? homeFromEnv(env);
26
+ const uploader = new LiveUploader({
27
+ home,
28
+ env,
29
+ fetchImpl: opts.fetchImpl,
30
+ sleepImpl: opts.sleepImpl,
31
+ debounceMs: opts.debounceMs,
32
+ stderr,
33
+ build: { item: parsed.number, jobDir },
34
+ onUnauthorized: opts.onFatal
35
+ });
36
+ const onLog = (event) => {
37
+ if (event.type === "changed" && event.file !== void 0) uploader.notify(event.file);
38
+ if (event.type === "error") stderr(`error ${event.file}: ${event.detail}
39
+ `);
40
+ };
41
+ const daemon = new CaptureDaemon({ ...opts.root === void 0 ? {} : { root: opts.root }, watchOnly: true, rescanMs: RESCAN_MS, onLog });
42
+ const pidfile = senderPidfile(home, parsed.number);
43
+ try {
44
+ mkdirSync(spoolDir(home), { recursive: true });
45
+ writeFileSync(pidfile, String(process.pid));
46
+ } catch {
47
+ }
48
+ await daemon.start();
49
+ let stopped;
50
+ return {
51
+ stop: () => stopped ??= (async () => {
52
+ daemon.rescanNow();
53
+ await uploader.stop(opts.stopWaitMs ?? SENDER_STOP_WAIT_MS);
54
+ await daemon.stop();
55
+ rmSync(pidfile, { force: true });
56
+ })()
57
+ };
58
+ }
59
+ async function runSessionSender() {
60
+ let sender;
61
+ const exit = async (code) => {
62
+ await sender?.stop().catch(() => void 0);
63
+ process.exit(code);
64
+ };
65
+ sender = await startSessionSender({ onFatal: () => void exit(1) });
66
+ process.on("SIGTERM", () => void exit(0));
67
+ process.on("SIGINT", () => void exit(0));
68
+ process.on("unhandledRejection", (reason) => {
69
+ process.stderr.write(`session-sender: unhandled error, continuing: ${String(reason)}
70
+ `);
71
+ });
72
+ await new Promise(() => void 0);
73
+ }
74
+
75
+ export { runSessionSender, senderPidfile, startSessionSender };