@outerlayer/cli 0.5.1 → 0.7.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 (92) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +19 -0
  3. package/dist/{agent-setup-IFH3FB5X.js → agent-setup-TXLDY5RV.js} +7 -6
  4. package/dist/{build-PAEKVYVK.js → build-JF5UOWKJ.js} +2 -1
  5. package/dist/build-info.json +1 -1
  6. package/dist/{check-4Q33B5ZE.js → check-ASZL5BEO.js} +39 -23
  7. package/dist/chunk-2VO25HHO.js +75 -0
  8. package/dist/{chunk-XDDW4FRS.js → chunk-5FWNEQNZ.js} +3 -3
  9. package/dist/{chunk-CHLVJNTE.js → chunk-6IYLDODX.js} +18 -6
  10. package/dist/{chunk-W7UTFZDH.js → chunk-6QWV3YDJ.js} +2 -2
  11. package/dist/{chunk-EGABNHZG.js → chunk-6ZZHWY2Z.js} +149 -18
  12. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  13. package/dist/chunk-7FTNIAHS.js +211 -0
  14. package/dist/{chunk-EMY4I27X.js → chunk-7ZR5UPMB.js} +555 -5
  15. package/dist/{chunk-F4NU4H3V.js → chunk-AII7CKTH.js} +133 -35
  16. package/dist/{chunk-ZXBITXTS.js → chunk-AIW6GLET.js} +1 -1
  17. package/dist/{chunk-UWH5DSUP.js → chunk-AJKEHABI.js} +507 -90
  18. package/dist/{chunk-RTXKYLCA.js → chunk-AQ54MKSQ.js} +2 -2
  19. package/dist/{chunk-Z62HNKEJ.js → chunk-CEBIA5FD.js} +31 -5
  20. package/dist/{chunk-EX6M5JPR.js → chunk-D6FXM5OW.js} +6302 -28611
  21. package/dist/{chunk-RMAZ2NWA.js → chunk-DGAIVMMN.js} +23 -2
  22. package/dist/{chunk-3DD6AOWC.js → chunk-DTXOJDEH.js} +26 -2
  23. package/dist/{chunk-T52PKE2Y.js → chunk-E4PIFIC3.js} +244 -17
  24. package/dist/{chunk-H5NCNVDA.js → chunk-EV3M3EH7.js} +2 -2
  25. package/dist/chunk-FBTE2A2X.js +38 -0
  26. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  27. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  28. package/dist/chunk-HKMPVPO7.js +55 -0
  29. package/dist/{chunk-UFFXNXLQ.js → chunk-JPR2WHHF.js} +31 -29
  30. package/dist/{chunk-ZMYPLFG3.js → chunk-M3SQ4YGD.js} +1 -1
  31. package/dist/{chunk-H4CK2YK4.js → chunk-MQPRB5K7.js} +18 -10
  32. package/dist/{chunk-AUFG23AA.js → chunk-OD2VTIHN.js} +19 -47
  33. package/dist/chunk-OUUINEYB.js +27 -0
  34. package/dist/chunk-PUN7LMNS.js +39 -0
  35. package/dist/{install-cmd-7WXOIFDO.js → chunk-Q5G2ODUZ.js} +1 -2
  36. package/dist/{chunk-B2G7JHHB.js → chunk-R7STSBLC.js} +1 -1
  37. package/dist/{chunk-OR6AEEDO.js → chunk-SSU747O6.js} +85 -25
  38. package/dist/chunk-T6OIJQRT.js +31 -0
  39. package/dist/{chunk-YYYXRJUW.js → chunk-TI7B7UEO.js} +1 -1
  40. package/dist/{chunk-WO2BXCTQ.js → chunk-VXLJORHL.js} +1 -1
  41. package/dist/{chunk-CBPPJSAR.js → chunk-WVGSB3Z2.js} +2 -2
  42. package/dist/{chunk-JLDZTG3A.js → chunk-X2FSFU4C.js} +4 -1
  43. package/dist/chunk-YNJEOGDL.js +22434 -0
  44. package/dist/{chunk-W4SQCOZU.js → chunk-ZOAEPWGL.js} +1 -1
  45. package/dist/{chunk-WOBNRVQT.js → chunk-ZYHKJKSU.js} +1 -1
  46. package/dist/{chunk-WIOZAJ2W.js → chunk-ZZXLM64C.js} +1 -1
  47. package/dist/{cli-LHSUYYMI.js → cli-LEVCGAQV.js} +154 -79
  48. package/dist/command-FLSYCGIB.js +187 -0
  49. package/dist/config-I3TVFYLH.js +10 -0
  50. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-MJMUONDY.js} +4 -4
  51. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-CE4KDXS3.js} +5 -4
  52. package/dist/{context-materialize-G2FUFC72.js → context-materialize-QL33HDJN.js} +5 -4
  53. package/dist/{dist-WDV7U7UJ.js → dist-PXBSA7QX.js} +2 -1
  54. package/dist/{doctor-MOA3RIEP.js → doctor-FVDT3KJZ.js} +46 -19
  55. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-7RPMRSVP.js} +13 -7
  56. package/dist/{emit-cmd-DCERAU27.js → emit-cmd-2QRMYPYO.js} +4 -3
  57. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-MKI7WETO.js} +12 -3
  58. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-RMLGZASB.js} +14 -3
  59. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-MTXAZ2GV.js} +15 -9
  60. package/dist/{exec-client-4XGXV3XN.js → exec-client-NPYNUAF3.js} +4 -3
  61. package/dist/{guest-init-GENRHNP7.js → guest-init-JWNNAH2H.js} +4 -3
  62. package/dist/{hook-fast-EYENPK6F.js → hook-fast-XBOZ3N6L.js} +1 -1
  63. package/dist/{import-capture-cmd-AVZ4VIVJ.js → import-capture-cmd-T4556ELW.js} +3 -2
  64. package/dist/{import-ruler-cmd-GUHL6IY5.js → import-ruler-cmd-SG3UVGI7.js} +2 -1
  65. package/dist/index.js +3 -2
  66. package/dist/{init-FYDJAKTM.js → init-BACXESVV.js} +21 -14
  67. package/dist/{init-2IPOIXJV.js → init-VLW7H6RW.js} +40 -62
  68. package/dist/{init-cmd-KLIDRVXQ.js → init-cmd-VJFB5L7X.js} +10 -9
  69. package/dist/inspect-XAUSB7L3.js +63 -0
  70. package/dist/install-NMAPLLBQ.js +14 -0
  71. package/dist/install-cmd-B5SEU3G7.js +8 -0
  72. package/dist/lima-7NPCNGWT.js +6 -0
  73. package/dist/{logs-7RYUH5A6.js → logs-H55NWW4H.js} +1 -0
  74. package/dist/loop-D3243JKT.js +64 -0
  75. package/dist/{machine-ECU3QCZD.js → machine-EB3GEVI4.js} +20 -13
  76. package/dist/session-sender-LMACEJJS.js +29 -0
  77. package/dist/{starter-pack-LIZYMKYQ.js → starter-pack-MUXMAU4S.js} +4 -3
  78. package/dist/{status-CLATHCTQ.js → status-F4THTJJK.js} +36 -16
  79. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-M6JA3OEE.js} +7 -7
  80. package/dist/{watch-3YW6Z3DQ.js → watch-NB5TGPEZ.js} +7 -7
  81. package/dist/{work-claim-cmd-3OPWA77E.js → work-claim-cmd-UHMWOS3G.js} +14 -7
  82. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-6UMCFTAW.js} +9 -3
  83. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-E4LEUIXZ.js} +9 -3
  84. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  85. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-OPZWGSEQ.js} +10 -4
  86. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-IDIAFFRU.js} +9 -2
  87. package/package.json +2 -1
  88. package/skill-pack/template/build/scripts/record-criteria.mjs +2 -1
  89. package/dist/chunk-D2KWEAN6.js +0 -55
  90. package/dist/chunk-ZOC7JSYC.js +0 -83
  91. package/dist/config-HL2AIR3K.js +0 -9
  92. package/dist/loop-NEBUGD2Q.js +0 -45
@@ -1,28 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- 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 { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
3
+ import { writeJobFile, readJobFile } from './chunk-T6OIJQRT.js';
4
+ import { senderPidfile } from './chunk-2VO25HHO.js';
5
+ import { DOCKERD_LOG } from './chunk-WVGSB3Z2.js';
6
+ import { installScrubContext } from './chunk-6ZZHWY2Z.js';
7
+ import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, cleanupContainer, outOfMemoryReason, syncInContainer, formatDuration, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, startCheckContainer, lifecycleSteps, lifecycleFailureReason, deleteBuildDisk, senderSignalScript } from './chunk-E4PIFIC3.js';
8
+ import { CLAUDE_CODE_VERSION, readBranchHead, fetchCommit } from './chunk-CEBIA5FD.js';
7
9
  import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
8
10
  import { childEnvironment } from './chunk-774HEQL6.js';
9
- import { waitForAgent, vmExecArgs } from './chunk-W4SQCOZU.js';
10
- 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';
14
- import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
11
+ import { waitForAgent, vmExecArgs } from './chunk-ZOAEPWGL.js';
12
+ import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-R7STSBLC.js';
13
+ import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-DGAIVMMN.js';
14
+ import { planArtifactUpload } from './chunk-ZYHKJKSU.js';
15
+ import { scrubText } from './chunk-D6FXM5OW.js';
16
+ import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm, DEFAULT_CHECK_TIME_LIMIT_MINUTES } from './chunk-DTXOJDEH.js';
15
17
  import { parseSize } from './chunk-F6GFZXZ3.js';
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';
18
+ import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, withTunnelRecord, RELAY_PORT } from './chunk-OD2VTIHN.js';
19
+ import { withHooksPath, authorEnvironment, commitIdentityRecord } from './chunk-HKMPVPO7.js';
20
+ import { parsePolicy } from './chunk-7ZR5UPMB.js';
17
21
  import { readCloudConfig } from './chunk-EABW6AJQ.js';
18
22
  import { resolveVersion } from './chunk-RA3O54FC.js';
19
23
  import { signerFor } from './chunk-M5POMKKW.js';
20
- import { runnerDir, buildDir, vsockPath, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
24
+ import { runnerDir, buildDir, vsockPath, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
21
25
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
22
26
  import { spawn, spawnSync } from 'child_process';
23
- import { randomBytes } from 'crypto';
24
- import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, existsSync, readFileSync, openSync, closeSync, rmSync, readdirSync } from 'fs';
25
- import { join, isAbsolute, dirname, delimiter } from 'path';
27
+ import { randomBytes, createHash } from 'crypto';
28
+ import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync, lstatSync, realpathSync } from 'fs';
29
+ import { join, isAbsolute, dirname, delimiter, sep } from 'path';
26
30
 
27
31
  createRequire(import.meta.url);
28
32
  var DEFAULT_MEMORY = "8g";
@@ -141,12 +145,20 @@ async function provisionMicroVm(input) {
141
145
  };
142
146
  input.save(state, environment);
143
147
  const bail = async (reason, aborted = false) => {
148
+ let why = reason;
149
+ if (!aborted && state.cgroup !== void 0) {
150
+ try {
151
+ if (runtime.vm.cgroup.oomKills(state.cgroup) > 0) why = outOfMemoryReason(config.build?.memory);
152
+ } catch (err) {
153
+ log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
154
+ }
155
+ }
144
156
  try {
145
157
  await cleanupMicroVm(runtime, state);
146
158
  } catch (err) {
147
159
  log.error(`${tag} cleanup failed: ${describeCallFailure(err)}`);
148
160
  }
149
- return aborted ? { ok: false, reason: "ended before provision finished", aborted: true } : fail(reason);
161
+ return aborted ? { ok: false, reason: "ended before provision finished", aborted: true } : fail(why);
150
162
  };
151
163
  if (input.aborted()) return bail("", true);
152
164
  let imageConfig;
@@ -192,9 +204,15 @@ async function provisionMicroVm(input) {
192
204
  return bail(`the build's disk and machine could not be made: ${describeCallFailure(err)}`);
193
205
  }
194
206
  if (input.aborted()) return bail("", true);
207
+ try {
208
+ state.cgroup = vm.cgroup.create(buildId, limits.memoryBytes);
209
+ } catch (err) {
210
+ return bail(`the build's memory limit could not be set: ${describeCallFailure(err)}`);
211
+ }
212
+ if (state.cgroup !== void 0) input.save(state, environment);
195
213
  const consolePath = join(jobDir, "vm-console.log");
196
214
  try {
197
- state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath });
215
+ state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath, cgroup: state.cgroup });
198
216
  } catch (err) {
199
217
  return bail(`Firecracker did not start: ${describeCallFailure(err)}`);
200
218
  }
@@ -315,7 +333,7 @@ function agentVersionOf(toolsJson) {
315
333
  }
316
334
  return CLAUDE_CODE_VERSION;
317
335
  }
318
- function microVmCommandLine(input) {
336
+ function vmExecFor(input, argv) {
319
337
  const state = input.job.microvm;
320
338
  const env = buildEnvironment({
321
339
  config: input.config,
@@ -334,13 +352,17 @@ function microVmCommandLine(input) {
334
352
  cwd: CHECKOUT_DIR,
335
353
  env: { ...state.imageEnv, ...env.literals },
336
354
  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 }
355
+ argv
343
356
  };
357
+ return { options, env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
358
+ }
359
+ function microVmCommandLine(input) {
360
+ const { options, env } = vmExecFor(input, ["/bin/sh", "-c", input.cmdline]);
361
+ return { line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "), options, env };
362
+ }
363
+ function microVmSenderCommand(input) {
364
+ const { options, env } = vmExecFor(input, ["outerlayer", "session-sender"]);
365
+ return { command: process.execPath, args: [input.cli, "vm-exec", ...vmExecArgs(options)], env };
344
366
  }
345
367
  function isRunning(runtime, state) {
346
368
  return state.firecracker !== void 0 && runtime.vm.firecracker.alive(state.firecracker);
@@ -391,6 +413,13 @@ async function cleanupMicroVm(runtime, state, jobDir) {
391
413
  failures.push(`the microVM (pid ${state.firecracker.pid}) was not stopped: ${describeCallFailure(err)}`);
392
414
  }
393
415
  }
416
+ if (state.cgroup !== void 0) {
417
+ try {
418
+ runtime.vm.cgroup.remove(state.cgroup);
419
+ } catch (err) {
420
+ failures.push(`the build's cgroup ${state.cgroup} was not removed: ${describeCallFailure(err)}`);
421
+ }
422
+ }
394
423
  if (state.buildDisk !== void 0) deleteBuildDisk(state.buildDisk);
395
424
  const tunnel = runtime.tunnels.get(state.buildId);
396
425
  if (tunnel !== void 0) {
@@ -583,70 +612,6 @@ async function closeHostBuild(runtime, build) {
583
612
  rmSync(build.buildDir, { recursive: true, force: true });
584
613
  if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
585
614
  }
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
615
 
651
616
  // src/runner/verdict.ts
652
617
  var VERDICT_SETTLE_MS = 5 * 6e4;
@@ -720,6 +685,308 @@ async function settleVerdict(deps) {
720
685
  await deps.sleep(everyMs);
721
686
  }
722
687
  }
688
+ var ENDED = "ended before provision finished";
689
+ async function provisionProcess(input) {
690
+ const { config, runtime, item, jobDir } = input;
691
+ if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
692
+ const repository = item.specRepository;
693
+ if (repository === void 0 || repository === null || repository === "") {
694
+ return { ok: false, reason: "the work item names no repository to clone" };
695
+ }
696
+ const buildId = randomBytes(6).toString("hex");
697
+ const buildDir2 = buildDir(config.configPath, buildId);
698
+ const workDir = join(jobDir, "work");
699
+ const state = { buildId, buildDir: buildDir2, workDir };
700
+ const environment = sharedUserEnvironment(input.cliVersion);
701
+ input.save(state, environment);
702
+ const bail = async (reason) => {
703
+ try {
704
+ await cleanupProcess(runtime, state);
705
+ } catch (err) {
706
+ input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
707
+ }
708
+ return { ok: false, reason };
709
+ };
710
+ if (input.aborted()) return bail(ENDED);
711
+ mkdirSync(buildDir2, { recursive: true });
712
+ chmodSync(buildDir2, 493);
713
+ try {
714
+ await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
715
+ } catch (err) {
716
+ return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
717
+ }
718
+ input.save(state, environment);
719
+ if (input.aborted()) return bail(ENDED);
720
+ const checkout = join(workDir, "checkout");
721
+ const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
722
+ const cloned = await runtime.clone({ remote, dest: checkout });
723
+ if (!cloned.ok) return bail(cloned.reason);
724
+ if (input.aborted()) return bail(ENDED);
725
+ return { ok: true, workdir: checkout, state, environment };
726
+ }
727
+ function cleanupProcess(runtime, state) {
728
+ return closeHostBuild(runtime, state);
729
+ }
730
+ var MAX_VALIDATOR_FILES = 20;
731
+ var VALIDATORS_DIR = ".outerlayer/validators";
732
+ var POLICY_FILE = ".outerlayer/policy.yaml";
733
+ var LIMIT_KEY_TAIL = " (runner.checks.timeLimitMinutes)";
734
+ function lastLine(text2) {
735
+ return text2.split(/\r\n|\r|\n/).map((line) => line.trim()).filter((line) => line !== "").at(-1);
736
+ }
737
+ function readInside(root, rel) {
738
+ const path = join(root, rel);
739
+ try {
740
+ if (!lstatSync(path).isFile()) return void 0;
741
+ const inside = realpathSync(path);
742
+ const base = realpathSync(root);
743
+ return inside.startsWith(base + sep) ? readFileSync(inside, "utf8") : void 0;
744
+ } catch {
745
+ return void 0;
746
+ }
747
+ }
748
+ function validatorFilesIn(root) {
749
+ const dir = join(root, VALIDATORS_DIR);
750
+ let names;
751
+ try {
752
+ if (!lstatSync(dir).isDirectory()) return [];
753
+ names = readdirSync(dir);
754
+ } catch {
755
+ return [];
756
+ }
757
+ const files = [];
758
+ for (const name of names.filter((entry) => /\.ya?ml$/.test(entry)).sort().slice(0, MAX_VALIDATOR_FILES)) {
759
+ const path = `${VALIDATORS_DIR}/${name}`;
760
+ const content = readInside(root, path);
761
+ if (content !== void 0) files.push({ path, content });
762
+ }
763
+ return files;
764
+ }
765
+ function checksIn(root, log, tag) {
766
+ const policy = readInside(root, POLICY_FILE);
767
+ const loaded = parsePolicy(policy === void 0 ? null : { path: POLICY_FILE, content: policy }, validatorFilesIn(root));
768
+ for (const problem of loaded.problems) log.error(`${tag} ${problem.file}: ${problem.problem}`);
769
+ const hostChecks = loaded.customs.filter((def) => def.runWhere === "host" && def.level !== "off" && def.declaresEmit !== null && def.command !== void 0);
770
+ return hostChecks.map((def) => ({ name: def.declaresEmit, command: def.command }));
771
+ }
772
+ async function readDefinitions(input, repository, scratch, token) {
773
+ const { config, job, deps } = input;
774
+ const tag = `#${job.item}`;
775
+ const stage = deps.stage ?? stageGovernedContext;
776
+ const controlPlaneUrl = (controlPlane) => deps.runtime.remoteFor?.(controlPlane) ?? `${deps.runtime.github.gitUrl}/${controlPlane}.git`;
777
+ const staged = await stage({
778
+ config,
779
+ repository,
780
+ buildId: `checks-${randomBytes(4).toString("hex")}`,
781
+ buildDir: scratch,
782
+ fetchImpl: deps.fetchImpl,
783
+ controlPlaneUrl
784
+ });
785
+ if (staged.kind === "refused") return { ok: false, reason: staged.reason };
786
+ if (staged.kind === "staged") {
787
+ try {
788
+ return { ok: true, checks: checksIn(join(staged.dir, "tree"), deps.log, tag) };
789
+ } finally {
790
+ rmSync(staged.dir, { recursive: true, force: true });
791
+ }
792
+ }
793
+ const repo = join(scratch, "repo");
794
+ const remote = deps.runtime.remoteFor?.(repository) ?? `${deps.runtime.github.gitUrl}/${repository}.git`;
795
+ const commit = job.environment?.recipe?.sourceCommit ?? await deps.runtime.readHead(remote, scratch);
796
+ if (commit === void 0) return { ok: false, reason: "the repository's default branch could not be read" };
797
+ const fetched = await (deps.fetchCommit ?? fetchCommit)({ remote, dir: repo, commit, token });
798
+ if (!fetched.ok) return { ok: false, reason: fetched.reason };
799
+ return { ok: true, checks: checksIn(repo, deps.log, tag) };
800
+ }
801
+ function timeoutAfter(ms, sleep) {
802
+ if (sleep !== void 0) return { done: sleep(ms).then(() => "timeout"), cancel: () => void 0 };
803
+ let timer;
804
+ const done = new Promise((resolve) => {
805
+ timer = setTimeout(() => resolve("timeout"), ms);
806
+ timer.unref();
807
+ });
808
+ return { done, cancel: () => clearTimeout(timer) };
809
+ }
810
+ function noCleanEnvironment(job) {
811
+ const ran = job.microvm !== void 0 ? "builtin:vm" : job.build?.workDir !== void 0 ? "builtin:process" : "a provision hook of the host's own";
812
+ return `the host has no clean environment for checks: this build ran under ${ran}, and checks run only in a new container from a container build's image`;
813
+ }
814
+ async function runHostChecks(input) {
815
+ const { config, job, jobDir, deps } = input;
816
+ const { runtime, log } = deps;
817
+ const tag = `#${job.item}`;
818
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
819
+ const repository = job.repository ?? job.container?.repository;
820
+ if (repository === void 0) return;
821
+ const jobFilePath = join(jobDir, "job.json");
822
+ const tokens = runtime.credentials.get(jobDir);
823
+ let readToken;
824
+ try {
825
+ readToken = await tokens?.token("read");
826
+ } catch (err) {
827
+ log.error(`${tag} no read token for the checks: ${describeCallFailure(err)}`);
828
+ }
829
+ const scratch = join(jobDir, "checks", "definitions");
830
+ rmSync(scratch, { recursive: true, force: true });
831
+ mkdirSync(scratch, { recursive: true });
832
+ let definitions;
833
+ try {
834
+ definitions = await readDefinitions(input, repository, scratch, readToken);
835
+ } catch (err) {
836
+ definitions = { ok: false, reason: describeCallFailure(err) };
837
+ } finally {
838
+ removeStagedContext(scratch);
839
+ }
840
+ if (!definitions.ok) {
841
+ log.line(`${tag} checks not run: the definitions could not be read: ${definitions.reason}`);
842
+ rmSync(scratch, { recursive: true, force: true });
843
+ return;
844
+ }
845
+ const checks = definitions.checks;
846
+ if (checks.length === 0) {
847
+ rmSync(scratch, { recursive: true, force: true });
848
+ return;
849
+ }
850
+ log.line(`${tag} checks started (${checks.map((check) => check.name).join(", ")})`);
851
+ const startedMs = now().getTime();
852
+ let signer;
853
+ const record = async (check, result, sentence, keyTail = "") => {
854
+ signer ??= await signerFor(config.configPath);
855
+ const body = sentence === void 0 ? void 0 : cutReasonLine(`${scrubReason(sentence, config) ?? "check failed"}${keyTail}`);
856
+ const clientEmitId = `host-${createHash("sha256").update(`${job.claimId}:${job.startedAt}:${check.name}`).digest("hex").slice(0, 40)}`;
857
+ const sent = await retrySend(
858
+ () => postJson(
859
+ config.url,
860
+ "/v1/emitted-results",
861
+ config.apiKey,
862
+ config.appId,
863
+ { schemaVersion: 1, emit: { clientEmitId, name: check.name, result, ...body === void 0 ? {} : { body }, emittedAt: now().toISOString(), item: { number: Number(job.item) } } },
864
+ deps.fetchImpl,
865
+ void 0,
866
+ signer
867
+ ),
868
+ { now: deps.now }
869
+ );
870
+ if (sent.ok) log.line(`${tag} check ${check.name} ${result}${body === void 0 ? "" : `: ${body}`}`);
871
+ else log.error(`${tag} check ${check.name} ${result} could not be recorded: ${sent.reason}`);
872
+ };
873
+ const failAll = async (reason, keyTail = "") => {
874
+ log.error(`${tag} checks failed: ${reason}${keyTail}`);
875
+ for (const check of checks) await record(check, "fail", reason, keyTail);
876
+ };
877
+ let container;
878
+ try {
879
+ if (job.container === void 0) return await failAll(noCleanEnvironment(job));
880
+ if (tokens === void 0) return await failAll("the build's repository tokens are no longer held, so the branch cannot be fetched");
881
+ const branch = job.branch;
882
+ if (branch === void 0) return await failAll("the build pushed no branch: the claim names none");
883
+ const remote = runtime.remoteFor?.(repository) ?? `${runtime.github.gitUrl}/${repository}.git`;
884
+ const read = await (deps.readBranch ?? readBranchHead)({ remote, dir: scratch, branch, token: readToken });
885
+ if (read.kind === "absent") return await failAll(`the build pushed no branch: ${branch} does not exist on ${repository}`);
886
+ if (read.kind === "unreadable") return await failAll(`the branch ${branch} could not be read: ${read.reason}`);
887
+ const limitMinutes = config.checks?.timeLimitMinutes ?? DEFAULT_CHECK_TIME_LIMIT_MINUTES;
888
+ const minutes = `${limitMinutes} ${limitMinutes === 1 ? "minute" : "minutes"}`;
889
+ const environmentLog = join(jobDir, "checks", "environment.log");
890
+ const setup = async () => {
891
+ const started = await startCheckContainer({
892
+ config,
893
+ runtime,
894
+ job,
895
+ jobDir,
896
+ tokens,
897
+ log,
898
+ save: (state) => {
899
+ job.checkContainer = state;
900
+ writeJobFile(jobFilePath, job);
901
+ }
902
+ });
903
+ if (!started.ok) return `the check environment could not be made: ${started.reason}`;
904
+ container = started.container;
905
+ const made = started.container;
906
+ const steps = [
907
+ {
908
+ argv: ["git", "config", "--global", `url.http://127.0.0.1:${RELAY_PORT}/github.com/.insteadOf`, "https://github.com/"],
909
+ workdir: "/build/work",
910
+ failed: (status) => `configuring git failed (exit ${status})`
911
+ },
912
+ {
913
+ argv: ["git", "clone", "--filter=blob:none", "--single-branch", "--branch", branch, `https://github.com/${repository}.git`, CHECKOUT_DIR],
914
+ workdir: "/build/work",
915
+ failed: (status) => `the clone of ${branch} failed (exit ${status})`
916
+ }
917
+ ];
918
+ for (const step of steps) {
919
+ const ran = await made.exec(step.argv, step.workdir, { logPath: environmentLog });
920
+ if (ran.status !== 0) return `the check environment could not be prepared: ${step.failed(ran.status)}`;
921
+ }
922
+ for (const step of lifecycleSteps(job.container?.lifecycle ?? {})) {
923
+ log.line(`${tag} checks lifecycle ${step.name} (${step.source})`);
924
+ const ran = await made.exec(step.argv, CHECKOUT_DIR, { logPath: environmentLog });
925
+ if (ran.status !== 0) return `the check environment could not be prepared: ${lifecycleFailureReason(step, ran.status)}`;
926
+ }
927
+ return made;
928
+ };
929
+ const setupTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
930
+ let settled;
931
+ try {
932
+ const outcome = await Promise.race([setup(), setupTimer.done]);
933
+ if (outcome === "timeout") return await failAll(`the check environment setup ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
934
+ settled = outcome;
935
+ } finally {
936
+ setupTimer.cancel();
937
+ }
938
+ if (typeof settled === "string") return await failAll(settled);
939
+ const ready = settled;
940
+ let usable = true;
941
+ for (const check of checks) {
942
+ if (!usable) {
943
+ await record(check, "fail", "the check environment could not be restarted after an earlier check ran out of time");
944
+ continue;
945
+ }
946
+ const checkStartedMs = now().getTime();
947
+ const running = ready.exec(["/bin/sh", "-c", check.command], CHECKOUT_DIR, { logPath: join(jobDir, "checks", `${check.name}.log`) });
948
+ running.catch(() => void 0);
949
+ const timer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
950
+ let outcome;
951
+ try {
952
+ outcome = await Promise.race([running, timer.done]);
953
+ } catch (err) {
954
+ timer.cancel();
955
+ await record(check, "fail", `the command could not be run: ${describeCallFailure(err)}`);
956
+ continue;
957
+ }
958
+ timer.cancel();
959
+ if (outcome === "timeout") {
960
+ await record(check, "fail", `ran out of time after ${minutes}`, LIMIT_KEY_TAIL);
961
+ const restarting = ready.restart().catch(() => false);
962
+ const restartTimer = timeoutAfter(limitMinutes * 6e4, deps.sleep);
963
+ usable = await Promise.race([restarting, restartTimer.done]) === true;
964
+ restartTimer.cancel();
965
+ continue;
966
+ }
967
+ if (outcome.status === 0) {
968
+ log.line(`${tag} check ${check.name} ran (${formatDuration(now().getTime() - checkStartedMs)})`);
969
+ await record(check, "pass");
970
+ continue;
971
+ }
972
+ const last = lastLine(outcome.stderr) ?? lastLine(outcome.stdout);
973
+ const scrubbed = last === void 0 ? void 0 : scrubReason(last, config);
974
+ await record(check, "fail", `exited ${outcome.status}${scrubbed === void 0 ? " with no output" : `: ${cutReasonLine(scrubbed, 140)}`}`);
975
+ }
976
+ } catch (err) {
977
+ log.error(`${tag} checks stopped: ${describeCallFailure(err)}`);
978
+ } finally {
979
+ if (container !== void 0 || job.checkContainer !== void 0) {
980
+ const state = container?.state ?? job.checkContainer;
981
+ await cleanupContainer(runtime, state).catch((err) => log.error(`${tag} removing the check environment failed: ${describeCallFailure(err)}`));
982
+ delete job.checkContainer;
983
+ writeJobFile(jobFilePath, job);
984
+ }
985
+ rmSync(scratch, { recursive: true, force: true });
986
+ if (existsSync(scratch)) log.error(`${tag} the checks' scratch directory could not be removed`);
987
+ log.line(`${tag} checks done (${formatDuration(now().getTime() - startedMs)})`);
988
+ }
989
+ }
723
990
 
724
991
  // src/runner/job.ts
725
992
  var LEASE_SECONDS = 900;
@@ -1167,6 +1434,11 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1167
1434
  const jobFilePath = join(jobDirPath, "job.json");
1168
1435
  const logPath = join(jobDirPath, "command.log");
1169
1436
  const tag = `#${job.item}`;
1437
+ if (job.sender !== void 0) {
1438
+ await stopSender({ job, deps: { spawnFn, runtime: deps.container, log } });
1439
+ delete job.sender;
1440
+ writeJobFile(jobFilePath, job);
1441
+ }
1170
1442
  const build = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.container ?? createContainerRuntime() };
1171
1443
  const vmBuild = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.container ?? createContainerRuntime() };
1172
1444
  if (build !== void 0 && job.pid !== void 0 && isGroupAlive(job.pid)) {
@@ -1179,7 +1451,7 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1179
1451
  await stopProcessGroup(job.pid);
1180
1452
  }
1181
1453
  if (outcome !== void 0 && job.endStage === void 0) {
1182
- const TEARDOWN_STAGES = ["sync", "cleanup", "report"];
1454
+ const TEARDOWN_STAGES = ["checks", "sync", "cleanup", "report"];
1183
1455
  const staleTeardownStage = job.stage !== void 0 && TEARDOWN_STAGES.includes(job.stage);
1184
1456
  job.endStage = ending?.stage ?? (staleTeardownStage ? job.exitCode !== void 0 ? "command" : void 0 : job.stage);
1185
1457
  job.endExitCode = ending?.exitCode ?? (outcome === "ok" || outcome === "failed" ? job.exitCode : void 0);
@@ -1189,6 +1461,44 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1189
1461
  job.reason = scrubReason(ending.reason, config);
1190
1462
  writeJobFile(jobFilePath, job);
1191
1463
  }
1464
+ if (job.checkContainer !== void 0 && job.checksRan === true) {
1465
+ try {
1466
+ await cleanupContainer(deps.container ?? createContainerRuntime(), job.checkContainer);
1467
+ delete job.checkContainer;
1468
+ writeJobFile(jobFilePath, job);
1469
+ } catch (err) {
1470
+ log.error(`${tag} removing an earlier attempt's check environment failed: ${describeCallFailure(err)} \xB7 continuing`);
1471
+ }
1472
+ }
1473
+ if (outcome === "ok" && !job.cleanupRan && job.checksRan !== true) {
1474
+ setStage(job, jobDirPath, "checks");
1475
+ job.checksRan = true;
1476
+ writeJobFile(jobFilePath, job);
1477
+ try {
1478
+ await runHostChecks({
1479
+ config,
1480
+ job,
1481
+ jobDir: jobDirPath,
1482
+ deps: { fetchImpl: deps.fetchImpl, runtime: deps.container ?? createContainerRuntime(), log, now: deps.now, ...deps.hostChecks }
1483
+ });
1484
+ } catch (err) {
1485
+ log.error(`${tag} the host's checks stopped: ${describeCallFailure(err)} \xB7 continuing`);
1486
+ }
1487
+ }
1488
+ let outOfMemory;
1489
+ if (vmBuild?.state.cgroup !== void 0 && outcome !== void 0 && outcome !== "ok" && ending?.reason === void 0) {
1490
+ try {
1491
+ if (vmBuild.runtime.vm.cgroup.oomKills(vmBuild.state.cgroup) > 0) {
1492
+ outOfMemory = outOfMemoryReason(config.build?.memory);
1493
+ }
1494
+ } catch (err) {
1495
+ log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
1496
+ }
1497
+ if (outOfMemory !== void 0) {
1498
+ job.reason = scrubReason(outOfMemory, config);
1499
+ writeJobFile(jobFilePath, job);
1500
+ }
1501
+ }
1192
1502
  const hookLimits = () => hookLimitsFor(config, now().getTime(), deps.now);
1193
1503
  let tunnelRecord;
1194
1504
  if (!job.cleanupRan) {
@@ -1352,6 +1662,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1352
1662
  const fromHook = readReasonLine(jobDirPath, config);
1353
1663
  if (verdictReason !== void 0) {
1354
1664
  job.reason = verdictReason;
1665
+ } else if (outOfMemory !== void 0) {
1666
+ job.reason = scrubReason(outOfMemory, config);
1355
1667
  } else if (ending?.reason === void 0) {
1356
1668
  job.reason = (outcome === "ok" ? void 0 : held?.reason) ?? (isolated ? fromHook ?? job.reason : fromHook);
1357
1669
  }
@@ -1470,4 +1782,109 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
1470
1782
  return recovered;
1471
1783
  }
1472
1784
 
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 };
1785
+ // src/runner/sender.ts
1786
+ var STOP_WAIT_MS = 3e4;
1787
+ var POLL_MS = 200;
1788
+ var BUILD_HOME = "/build/home";
1789
+ async function startSender(input) {
1790
+ const { config, job, jobDir, deps } = input;
1791
+ const tag = `#${job.item}`;
1792
+ if (job.itemKey === void 0) return void 0;
1793
+ const spawnFn = deps.spawnFn ?? spawn;
1794
+ try {
1795
+ const launch = launchFor(config, job, jobDir, deps);
1796
+ const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
1797
+ let pid;
1798
+ try {
1799
+ const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
1800
+ child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
1801
+ child.on("exit", (code, signal) => deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`));
1802
+ child.unref?.();
1803
+ pid = child.pid;
1804
+ } finally {
1805
+ closeSync(fd);
1806
+ }
1807
+ if (pid === void 0) {
1808
+ deps.log.line(`${tag} session sender did not start: no pid`);
1809
+ return void 0;
1810
+ }
1811
+ deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
1812
+ return { pid, start: startTimeOf(pid) ?? void 0 };
1813
+ } catch (err) {
1814
+ deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
1815
+ return void 0;
1816
+ }
1817
+ }
1818
+ function launchFor(config, job, jobDir, deps) {
1819
+ if (job.microvm !== void 0) {
1820
+ const runtime = deps.runtime ?? createContainerRuntime();
1821
+ const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
1822
+ return { ...microVmSenderCommand({ job, runtime, config, cli }), cwd: jobDir };
1823
+ }
1824
+ if (job.container !== void 0) {
1825
+ return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
1826
+ }
1827
+ const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
1828
+ const build = job.build;
1829
+ const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
1830
+ if (build?.exec !== void 0) {
1831
+ return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
1832
+ }
1833
+ return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
1834
+ }
1835
+ function isLive(sender) {
1836
+ if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
1837
+ try {
1838
+ process.kill(sender.pid, 0);
1839
+ return true;
1840
+ } catch {
1841
+ return false;
1842
+ }
1843
+ }
1844
+ function signalHost(sender, signal) {
1845
+ if (!isLive(sender)) return;
1846
+ try {
1847
+ process.kill(-sender.pid, signal);
1848
+ } catch {
1849
+ try {
1850
+ process.kill(sender.pid, signal);
1851
+ } catch {
1852
+ }
1853
+ }
1854
+ }
1855
+ async function signalInGuest(runtime, state, pidfile, signal) {
1856
+ await runtime.vm.exec(state.vsockPath, {
1857
+ argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
1858
+ user: state.agentUser ?? ADDED_USER,
1859
+ cwd: "/",
1860
+ env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
1861
+ });
1862
+ }
1863
+ async function stopSender(input) {
1864
+ const { job, deps } = input;
1865
+ const sender = job.sender;
1866
+ if (sender === void 0) return;
1867
+ const tag = `#${job.item}`;
1868
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1869
+ const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1870
+ const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
1871
+ const signalInside = async (signal) => {
1872
+ if (inContainer !== void 0) await signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
1873
+ if (inGuest !== void 0) await signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
1874
+ };
1875
+ try {
1876
+ if (inContainer !== void 0 || inGuest !== void 0) await signalInside("TERM");
1877
+ else signalHost(sender, "SIGTERM");
1878
+ const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
1879
+ for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) await sleep(POLL_MS);
1880
+ if (isLive(sender)) {
1881
+ deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
1882
+ await signalInside("KILL");
1883
+ signalHost(sender, "SIGKILL");
1884
+ }
1885
+ } catch (err) {
1886
+ deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
1887
+ }
1888
+ }
1889
+
1890
+ 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 };