@outerlayer/cli 0.5.0 → 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 (70) hide show
  1. package/CHANGELOG.md +17 -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-AXCXDYO2.js → check-CCVPBN7A.js} +26 -14
  6. package/dist/{chunk-5Y3QQRUH.js → chunk-3DD6AOWC.js} +37 -0
  7. package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
  8. package/dist/{chunk-L24G4PLP.js → chunk-43J3M6KB.js} +486 -21
  9. package/dist/{chunk-FC7TMRNA.js → chunk-4NLKY4WR.js} +2 -2
  10. package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
  11. package/dist/{chunk-D2KWEAN6.js → chunk-7DBI6SNT.js} +1 -1
  12. package/dist/{chunk-QDQEUVUF.js → chunk-AIW6GLET.js} +1 -1
  13. package/dist/{chunk-EX6M5JPR.js → chunk-ANK2B36Y.js} +131 -15
  14. package/dist/{chunk-RMAZ2NWA.js → chunk-BNMCXPG7.js} +14 -2
  15. package/dist/{chunk-UFFXNXLQ.js → chunk-FA2BOX4O.js} +31 -29
  16. package/dist/chunk-FBTE2A2X.js +38 -0
  17. package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
  18. package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
  19. package/dist/{chunk-JLDZTG3A.js → chunk-IASWYRVG.js} +1 -1
  20. package/dist/{install-cmd-7WXOIFDO.js → chunk-JWOQNNN2.js} +0 -1
  21. package/dist/{chunk-ZLB7GCTY.js → chunk-MIGEDH5P.js} +19 -11
  22. package/dist/{chunk-OHFDWLJP.js → chunk-MXB5VB4Y.js} +18 -6
  23. package/dist/chunk-NQXFAGHU.js +211 -0
  24. package/dist/chunk-OUUINEYB.js +27 -0
  25. package/dist/{chunk-OR6AEEDO.js → chunk-PMAMOSTP.js} +3 -3
  26. package/dist/{chunk-RJ467PDQ.js → chunk-RBOMWNOG.js} +1 -1
  27. package/dist/{chunk-4NMBDRBS.js → chunk-RZSF5B46.js} +289 -130
  28. package/dist/chunk-T6OIJQRT.js +31 -0
  29. package/dist/chunk-UGP76IZ5.js +34 -0
  30. package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
  31. package/dist/{chunk-RWOTHXYE.js → chunk-VMJ3MMBX.js} +206 -71
  32. package/dist/chunk-YYYRBYWG.js +32 -0
  33. package/dist/chunk-ZILGTTY6.js +75 -0
  34. package/dist/{cli-NNWPFP5S.js → cli-7WCNPBYC.js} +147 -69
  35. package/dist/command-ARFK3OYF.js +162 -0
  36. package/dist/{config-XVJYZ4IQ.js → config-HL2AIR3K.js} +1 -1
  37. package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-PCESZLTR.js} +4 -4
  38. package/dist/{context-adopt-UMA4O5NY.js → context-adopt-G5JAELE7.js} +2 -2
  39. package/dist/{context-materialize-G2FUFC72.js → context-materialize-3HLPZAF6.js} +2 -2
  40. package/dist/{dist-WDV7U7UJ.js → dist-RDLP4GGJ.js} +1 -1
  41. package/dist/{doctor-J3GIMR7K.js → doctor-VOMYOHQU.js} +47 -13
  42. package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-TBF46VXZ.js} +6 -7
  43. package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-3427XKFW.js} +3 -3
  44. package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-GSI75K36.js} +3 -3
  45. package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-3PRQKB55.js} +6 -7
  46. package/dist/{hook-fast-EYENPK6F.js → hook-fast-NJBBZQ5Q.js} +1 -1
  47. package/dist/index.js +3 -2
  48. package/dist/{init-CZX7ZDWF.js → init-OHYFIKZD.js} +27 -56
  49. package/dist/{init-3ZQLFPNC.js → init-WVR4BZQ4.js} +8 -8
  50. package/dist/{init-cmd-W5NOLQPX.js → init-cmd-HMFNIFPI.js} +7 -7
  51. package/dist/inspect-XAUSB7L3.js +63 -0
  52. package/dist/install-DSUZE2TJ.js +13 -0
  53. package/dist/install-cmd-453C7IZT.js +8 -0
  54. package/dist/lima-7NPCNGWT.js +6 -0
  55. package/dist/{loop-VGM772I7.js → loop-UAJB34KA.js} +26 -11
  56. package/dist/{machine-5WEJA36F.js → machine-2JOWGRSZ.js} +7 -7
  57. package/dist/record-C43U4PKS.js +7 -0
  58. package/dist/session-sender-ED26V45B.js +28 -0
  59. package/dist/{status-KBDVUYQ3.js → status-6WNXX2O7.js} +42 -10
  60. package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-YONVGUYS.js} +6 -7
  61. package/dist/{watch-3YW6Z3DQ.js → watch-HHKAF26L.js} +6 -7
  62. package/dist/{work-claim-cmd-BQPPURBW.js → work-claim-cmd-LXBZTMJA.js} +6 -6
  63. package/dist/{work-cmd-NUR4CAG6.js → work-cmd-OVNF44FY.js} +3 -3
  64. package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-6DGQNTAH.js} +3 -3
  65. package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
  66. package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-FCZHFHZS.js} +4 -4
  67. package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-QJJVHZDC.js} +2 -2
  68. package/package.json +2 -1
  69. package/dist/chunk-ZOC7JSYC.js +0 -83
  70. package/dist/{chunk-XDDW4FRS.js → chunk-OIRNH6UO.js} +1 -1
@@ -1,29 +1,104 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { applyContextProvenance, installScrubContext, prepareSessionForUpload, wholeSessionCounts, sliceTurnIo } from './chunk-ZOC7JSYC.js';
4
- import { LaunchFilter } from './chunk-BCJNHMZT.js';
3
+ import { LaunchFilter } from './chunk-HHJDN6AU.js';
5
4
  import { RepoFilter } from './chunk-TBT347UY.js';
6
5
  import { hookExecSpoolPath } from './chunk-F7CU5ABH.js';
7
6
  import { applyReportedCost } from './chunk-BKO6JEZI.js';
8
- import { migrateLegacyArtifactSidecars, planArtifactUpload, artifactsStatePath, sanitizeTerminal, artifactBlobsDir, resolveArtifactTurnIndex, isArtifactTombstoned, ARTIFACT_RETRY_MAX_AGE_MS, writeArtifactSpoolState, reclaimUnneededArtifactBlobs, neededArtifactShas } from './chunk-WOBNRVQT.js';
9
- import { tailTranscript, enrichSessionRepo, CAPTURE_TIERS, scanAll, setRepoRoot, resolveRepoIdentity, inferArtifactKind, bannedPathsForTier, sessionIdFromPath, SOURCE_ADAPTERS, EVENT_TYPES } from './chunk-EX6M5JPR.js';
7
+ import { migrateLegacyArtifactSidecars, planArtifactUpload, artifactsStatePath, sanitizeTerminal, artifactBlobsDir, resolveArtifactTurnIndex, isArtifactTombstoned, ARTIFACT_RETRY_MAX_AGE_MS, writeArtifactSpoolState, reclaimUnneededArtifactBlobs, neededArtifactShas } from './chunk-3DEVYTNQ.js';
8
+ import { setCustomScrubbers, setLocalIdentity, tailTranscript, enrichSessionRepo, CAPTURE_TIERS, scanAll, setRepoRoot, resolveRepoIdentity, inferArtifactKind, bannedPathsForTier, sessionIdFromPath, scrubDeep, downconvertSession, isHumanUserTurn, turnCountBound, SOURCE_ADAPTERS, EVENT_TYPES } from './chunk-ANK2B36Y.js';
10
9
  import { C } from './chunk-YRYMNBGQ.js';
11
- import { readLaunchSpoolState, launchStatusIsDecisive, launchedItemsBySession, launchesPath } from './chunk-DVDEBNQJ.js';
10
+ import { readLaunchSpoolState, launchStatusIsDecisive, launchedItemsBySession, launchesPath } from './chunk-GQD22KKN.js';
12
11
  import { readLatestProvenanceForSession, readContextProvenance } from './chunk-ZJSNLVCE.js';
13
12
  import { defaultFetch } from './chunk-BCLJSQCV.js';
14
13
  import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
15
14
  import { readCloudConfig, cloudConfigPath, writeCloudConfig } from './chunk-EABW6AJQ.js';
16
15
  import { signerFor, signRunnerRequest } from './chunk-M5POMKKW.js';
17
- import { writeSyncStatus, spoolDir } from './chunk-UFFXNXLQ.js';
16
+ import { writeSyncStatus, spoolDir } from './chunk-FA2BOX4O.js';
18
17
  import { removeJournalHooks } from './chunk-XCCLFVXM.js';
19
18
  import { readRepoConnection, findGitDirs } from './chunk-BJ3KTMDH.js';
20
19
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
21
20
  import { dirname, basename, join } from 'path';
22
21
  import { statSync, readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync, rmSync } from 'fs';
22
+ import { execFileSync } from 'child_process';
23
+ import { hostname, userInfo } from 'os';
23
24
  import { fileURLToPath } from 'url';
24
25
  import { createRequire as createRequire$1 } from 'module';
25
26
 
26
27
  createRequire(import.meta.url);
28
+ function detectCi(env = process.env) {
29
+ return env.CI === "true" || env.CI === "1" || !!env.JENKINS_URL;
30
+ }
31
+ function gitGlobalConfig(key) {
32
+ try {
33
+ return execFileSync("git", ["config", "--global", key], { encoding: "utf8", timeout: 2e3 }).trim() || void 0;
34
+ } catch {
35
+ return void 0;
36
+ }
37
+ }
38
+ function installScrubContext(home, scrub) {
39
+ setCustomScrubbers(scrub);
40
+ setLocalIdentity({
41
+ name: gitGlobalConfig("user.name"),
42
+ email: gitGlobalConfig("user.email"),
43
+ username: (() => {
44
+ try {
45
+ return userInfo().username;
46
+ } catch {
47
+ return void 0;
48
+ }
49
+ })(),
50
+ hostname: (() => {
51
+ try {
52
+ return hostname();
53
+ } catch {
54
+ return void 0;
55
+ }
56
+ })(),
57
+ home
58
+ });
59
+ }
60
+ function applyContextProvenance(session, record) {
61
+ if (!record) return;
62
+ if (record.repository !== void 0) session.env.contextSourceRepo = record.repository;
63
+ if (record.ref !== void 0) session.env.contextSourceRef = record.ref;
64
+ if (record.sha !== null) session.env.contextSourceSha = record.sha;
65
+ session.env.contextSourceVerification = record.verification;
66
+ }
67
+ function prepareSessionForUpload(session, tier, envVars = process.env) {
68
+ setRepoRoot(resolveRepoIdentity(session.env.cwd).repoRoot);
69
+ scrubDeep(session);
70
+ session.workerKind ??= detectCi(envVars) ? "ci" : "seat";
71
+ return downconvertSession(session, tier);
72
+ }
73
+ var MAX_TOOL_IO_CHARS = 64e3;
74
+ function sliceToolIo(value) {
75
+ const text = typeof value === "string" ? value : JSON.stringify(value);
76
+ if (typeof text !== "string" || text.length <= MAX_TOOL_IO_CHARS) return value;
77
+ return `${text.slice(0, MAX_TOOL_IO_CHARS)}\u2026[sliced: ${text.length} chars exceeded the sync per-call cap]`;
78
+ }
79
+ function sliceTurnIo(turn) {
80
+ return {
81
+ ...turn,
82
+ toolCalls: (turn.toolCalls ?? []).map((call) => ({
83
+ ...call,
84
+ ...call.input !== void 0 ? { input: sliceToolIo(call.input) } : {},
85
+ ...call.output !== void 0 ? { output: sliceToolIo(call.output) } : {}
86
+ }))
87
+ };
88
+ }
89
+ function wholeSessionCounts(turns) {
90
+ const allCalls = turns.flatMap((t) => t.toolCalls ?? []);
91
+ return {
92
+ // An index BOUND (`1 + max(turn.index)`), not the shipped turn count —
93
+ // turn indexes are not guaranteed dense from zero, and a late artifact
94
+ // anchored at its real turn index must still validate against the
95
+ // summary row this produces (see turnCountBound).
96
+ turns: turnCountBound(turns),
97
+ toolCalls: allCalls.length,
98
+ errors: allCalls.filter((c) => c.status === "error").length,
99
+ userTurns: turns.filter(isHumanUserTurn).length
100
+ };
101
+ }
27
102
  var IRRELEVANT = /* @__PURE__ */ new Set(["__tests__", "__mocks__", "node_modules"]);
28
103
  function newestSourceMs(dir) {
29
104
  let newest = 0;
@@ -481,6 +556,8 @@ function retryAfterMs(response) {
481
556
  }
482
557
  var LiveUploader = class {
483
558
  constructor(opts) {
559
+ /** Set by `stop()`: no more debounce or retry waits, so what is pending is sent once and given up on. */
560
+ this.stopping = false;
484
561
  this.cycles = /* @__PURE__ */ new Map();
485
562
  /** Sessions this sender is done with: ended, or permanently rejected. The
486
563
  * daemon keeps mirroring a transcript after its session closes, and every
@@ -491,12 +568,31 @@ var LiveUploader = class {
491
568
  * every session, so the sender stops entirely for the rest of this
492
569
  * process rather than retrying session by session. */
493
570
  this.stopped = false;
571
+ this.warned = /* @__PURE__ */ new Set();
494
572
  this.home = opts.home;
495
573
  this.fetchImpl = opts.fetchImpl ?? fetch;
496
574
  this.sleepImpl = opts.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
497
575
  this.debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
498
576
  this.env = opts.env ?? process.env;
499
577
  this.stderr = opts.stderr ?? ((s) => void process.stderr.write(s));
578
+ this.build = opts.build;
579
+ this.onUnauthorized = opts.onUnauthorized;
580
+ }
581
+ /**
582
+ * Final pass for a build sender: sends what is pending now, without waiting out the debounce and without
583
+ * retrying a failed send, and resolves once every cycle has finished or `timeoutMs` has passed.
584
+ */
585
+ async stop(timeoutMs) {
586
+ this.stopping = true;
587
+ let timer;
588
+ const expired = new Promise((resolve) => {
589
+ timer = setTimeout(resolve, timeoutMs);
590
+ });
591
+ try {
592
+ await Promise.race([this.flush(), expired]);
593
+ } finally {
594
+ clearTimeout(timer);
595
+ }
500
596
  }
501
597
  /** Called for every daemon `mirrored` event. Cheap and synchronous — the
502
598
  * actual read/send work happens on the debounced cycle. */
@@ -536,7 +632,7 @@ var LiveUploader = class {
536
632
  for (; ; ) {
537
633
  for (; ; ) {
538
634
  const generationAtWaitStart = cycle.generation;
539
- await this.sleepImpl(this.debounceMs);
635
+ if (!this.stopping) await this.sleepImpl(this.debounceMs);
540
636
  if (cycle.generation !== generationAtWaitStart) continue;
541
637
  break;
542
638
  }
@@ -562,8 +658,21 @@ var LiveUploader = class {
562
658
  const priorEntry = state.sessions[key];
563
659
  if (priorEntry?.ended || priorEntry?.failed) return true;
564
660
  const cloudConfig = readCloudConfig(this.home);
565
- if (cloudConfig.autoSync === false) return false;
566
- if (!cloudConfig.url || !(cloudConfig.apiKey || cloudConfig.accountToken)) return false;
661
+ let destination;
662
+ if (this.build === void 0) {
663
+ if (cloudConfig.autoSync === false) return false;
664
+ if (!cloudConfig.url || !(cloudConfig.apiKey || cloudConfig.accountToken)) return false;
665
+ } else {
666
+ const url = this.env.OUTERLAYER_URL;
667
+ const credential = this.env.OUTERLAYER_API_KEY;
668
+ const appId = this.env.OUTERLAYER_APP_ID;
669
+ if (!url || !credential || !appId) {
670
+ this.warnOnce("incomplete", `live upload is idle: this build's environment lacks ${[!url && "OUTERLAYER_URL", !credential && "OUTERLAYER_API_KEY", !appId && "OUTERLAYER_APP_ID"].filter(Boolean).join(", ")}, so nothing streams until \`outerlayer sync\` runs.
671
+ `);
672
+ return false;
673
+ }
674
+ destination = { ok: true, url, credential, appId, kind: "key" };
675
+ }
567
676
  const info = subagentInfo(mirrorPath);
568
677
  const tail = tailTranscript(mirrorPath, void 0, {
569
678
  isSubagent: info.isSubagent,
@@ -573,17 +682,16 @@ var LiveUploader = class {
573
682
  if (!tail.session) return false;
574
683
  const session = tail.session;
575
684
  enrichSessionRepo(session);
576
- const destination = resolveGatewayCredentials({ home: this.home, env: {}, cwd: session.env.cwd ?? "/" });
685
+ destination ??= resolveGatewayCredentials({ home: this.home, env: {}, cwd: session.env.cwd ?? "/" });
577
686
  if (!destination.ok) return false;
578
- const repoFilter = new RepoFilter(cloudConfig.repos);
579
- if (!repoFilter.allows(session.env.gitRepo)) return false;
687
+ if (this.build === void 0 && !new RepoFilter(cloudConfig.repos).allows(session.env.gitRepo)) return false;
580
688
  const launchSpool = readLaunchSpoolState(this.home);
581
689
  if (!launchStatusIsDecisive(launchSpool)) return false;
582
- const launchFilter = new LaunchFilter(launchSpool.ids);
690
+ const launchFilter = new LaunchFilter(launchSpool.ids, launchSpool.records, this.build);
583
691
  if (!launchFilter.allows(session)) return false;
584
692
  let tier;
585
693
  try {
586
- tier = resolveTier(cloudConfig.tier);
694
+ tier = resolveTier(this.build === void 0 ? cloudConfig.tier : this.env.OUTERLAYER_TIER ?? cloudConfig.tier);
587
695
  } catch (err) {
588
696
  if (err instanceof SyncConfigError) return false;
589
697
  throw err;
@@ -613,6 +721,7 @@ var LiveUploader = class {
613
721
  const outcome = await this.sendWithRetry(endpoint, destination.credential, destination.appId, prepared.id, payload);
614
722
  if (outcome === "unauthorized") {
615
723
  this.stopped = true;
724
+ this.onUnauthorized?.();
616
725
  this.stderr(
617
726
  destination.credential.startsWith("olu_") ? `live upload stopped: the login was refused (expired or revoked). No session streams from this daemon until you run \`outerlayer login\` and it restarts.
618
727
  ` : `live upload stopped: the API key was refused. No session streams from this daemon until it restarts with a valid key.
@@ -620,8 +729,9 @@ var LiveUploader = class {
620
729
  );
621
730
  return true;
622
731
  }
732
+ if (outcome === "unreachable") return false;
623
733
  if (outcome === "rejected") {
624
- const reason = "the server refused this delta permanently";
734
+ const reason = `the server refused this delta permanently${this.lastRejectStatus === void 0 ? "" : ` (HTTP ${this.lastRejectStatus})`}`;
625
735
  this.mergeSessionState(key, {
626
736
  ackedTurnIndex: currentAcked,
627
737
  failed: true,
@@ -654,6 +764,7 @@ var LiveUploader = class {
654
764
  * key bound to a host key is refused on every route unless that key signed the request. A login token is
655
765
  * never signed: only the saved factory key is bound to a host key. */
656
766
  async signatureHeaders(endpoint, body, credential) {
767
+ if (this.build !== void 0) return {};
657
768
  if (readCloudConfig(this.home).apiKey !== credential) return {};
658
769
  let signer;
659
770
  try {
@@ -664,6 +775,12 @@ var LiveUploader = class {
664
775
  if (signer === void 0) return {};
665
776
  return { ...await signRunnerRequest({ method: "POST", url: endpoint, body: new TextEncoder().encode(body), signer }) };
666
777
  }
778
+ /** Writes `message` the first time `key` is seen, so a condition that holds on every fire logs once. */
779
+ warnOnce(key, message) {
780
+ if (this.warned.has(key)) return;
781
+ this.warned.add(key);
782
+ this.stderr(message);
783
+ }
667
784
  /** Sends one request, retrying a network error or a retryable 5xx forever
668
785
  * with exponential backoff (2s → 60s cap) — the daemon runs unattended, so
669
786
  * there is no bounded attempt count. A 401 or a permanent 4xx/rejection
@@ -673,6 +790,7 @@ var LiveUploader = class {
673
790
  let delayMs = RETRY_BASE_DELAY_MS;
674
791
  for (; ; ) {
675
792
  let response;
793
+ let failure = "";
676
794
  try {
677
795
  const signature = await this.signatureHeaders(endpoint, body, apiKey);
678
796
  response = await this.fetchImpl(endpoint, {
@@ -685,18 +803,30 @@ var LiveUploader = class {
685
803
  },
686
804
  body
687
805
  });
688
- } catch {
806
+ } catch (err) {
689
807
  response = void 0;
808
+ failure = err instanceof Error ? err.message : String(err);
690
809
  }
691
810
  if (response) {
692
811
  if (response.status === 401) return "unauthorized";
693
812
  if (response.ok) {
694
813
  const accepted = await acceptedSessionIds(response);
695
- if (accepted) return accepted.includes(sessionId) ? "accepted" : "rejected";
814
+ if (accepted) {
815
+ if (accepted.includes(sessionId)) return "accepted";
816
+ this.lastRejectStatus = void 0;
817
+ return "rejected";
818
+ }
696
819
  } else if (!RETRYABLE_STATUSES.has(response.status)) {
820
+ this.lastRejectStatus = response.status;
697
821
  return "rejected";
698
822
  }
699
823
  }
824
+ if (this.build !== void 0) {
825
+ const why = response === void 0 ? failure : `the gateway answered ${response.status}`;
826
+ this.stderr(`live upload of session ${sessionId} failed: ${why}${this.stopping ? "" : " \xB7 retrying"}
827
+ `);
828
+ }
829
+ if (this.stopping) return "unreachable";
700
830
  await this.sleepImpl(Math.max(retryAfterMs(response), delayMs));
701
831
  delayMs = Math.min(delayMs * 2, RETRY_MAX_DELAY_MS);
702
832
  }
@@ -1683,4 +1813,4 @@ ${C.yellow}!${C.reset} batch ${shippedBatches + 1}/${batches.length} accounting
1683
1813
  }
1684
1814
  }
1685
1815
 
1686
- export { CHUNK_PART_TARGET_BYTES, LiveUploader, RETRYABLE_STATUSES, SyncConfigError, SyncTransportError, isLoopbackUrl, resolveTier, runSync, staleBuildRefusal, staleBuildWarning };
1816
+ export { CHUNK_PART_TARGET_BYTES, LiveUploader, RETRYABLE_STATUSES, SyncConfigError, SyncTransportError, detectCi, installScrubContext, isLoopbackUrl, resolveTier, runSync, staleBuildRefusal, staleBuildWarning };
@@ -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-4NMBDRBS.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';
14
- import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-5Y3QQRUH.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';
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) {
@@ -410,6 +429,8 @@ async function stopOrphanedMicroVms(input) {
410
429
  process.kill(-job.pid, "SIGKILL");
411
430
  } catch {
412
431
  }
432
+ const deadline = Date.now() + 2e3;
433
+ while (isSameProcess(job.pid, job.start) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 10));
413
434
  }
414
435
  if (state.firecracker !== void 0 && input.runtime.vm.firecracker.alive(state.firecracker)) {
415
436
  await input.runtime.vm.firecracker.stop(state.firecracker);
@@ -581,60 +602,6 @@ async function closeHostBuild(runtime, build) {
581
602
  rmSync(build.buildDir, { recursive: true, force: true });
582
603
  if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
583
604
  }
584
- function readJobFile(path) {
585
- if (!existsSync(path)) return null;
586
- try {
587
- return JSON.parse(readFileSync(path, "utf8"));
588
- } catch {
589
- return null;
590
- }
591
- }
592
- function writeJobFile(path, job) {
593
- mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
594
- writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
595
- }
596
- var ENDED = "ended before provision finished";
597
- async function provisionProcess(input) {
598
- const { config, runtime, item, jobDir } = input;
599
- if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
600
- const repository = item.specRepository;
601
- if (repository === void 0 || repository === null || repository === "") {
602
- return { ok: false, reason: "the work item names no repository to clone" };
603
- }
604
- const buildId = randomBytes(6).toString("hex");
605
- const buildDir2 = buildDir(config.configPath, buildId);
606
- const workDir = join(jobDir, "work");
607
- const state = { buildId, buildDir: buildDir2, workDir };
608
- const environment = sharedUserEnvironment(input.cliVersion);
609
- input.save(state, environment);
610
- const bail = async (reason) => {
611
- try {
612
- await cleanupProcess(runtime, state);
613
- } catch (err) {
614
- input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
615
- }
616
- return { ok: false, reason };
617
- };
618
- if (input.aborted()) return bail(ENDED);
619
- mkdirSync(buildDir2, { recursive: true });
620
- chmodSync(buildDir2, 493);
621
- try {
622
- await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
623
- } catch (err) {
624
- return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
625
- }
626
- input.save(state, environment);
627
- if (input.aborted()) return bail(ENDED);
628
- const checkout = join(workDir, "checkout");
629
- const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
630
- const cloned = await runtime.clone({ remote, dest: checkout });
631
- if (!cloned.ok) return bail(cloned.reason);
632
- if (input.aborted()) return bail(ENDED);
633
- return { ok: true, workdir: checkout, state, environment };
634
- }
635
- function cleanupProcess(runtime, state) {
636
- return closeHostBuild(runtime, state);
637
- }
638
605
 
639
606
  // src/runner/verdict.ts
640
607
  var VERDICT_SETTLE_MS = 5 * 6e4;
@@ -708,6 +675,48 @@ async function settleVerdict(deps) {
708
675
  await deps.sleep(everyMs);
709
676
  }
710
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
+ }
711
720
 
712
721
  // src/runner/job.ts
713
722
  var LEASE_SECONDS = 900;
@@ -1155,6 +1164,11 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1155
1164
  const jobFilePath = join(jobDirPath, "job.json");
1156
1165
  const logPath = join(jobDirPath, "command.log");
1157
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
+ }
1158
1172
  const build = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.container ?? createContainerRuntime() };
1159
1173
  const vmBuild = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.container ?? createContainerRuntime() };
1160
1174
  if (build !== void 0 && job.pid !== void 0 && isGroupAlive(job.pid)) {
@@ -1177,6 +1191,20 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1177
1191
  job.reason = scrubReason(ending.reason, config);
1178
1192
  writeJobFile(jobFilePath, job);
1179
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
+ }
1180
1208
  const hookLimits = () => hookLimitsFor(config, now().getTime(), deps.now);
1181
1209
  let tunnelRecord;
1182
1210
  if (!job.cleanupRan) {
@@ -1340,6 +1368,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
1340
1368
  const fromHook = readReasonLine(jobDirPath, config);
1341
1369
  if (verdictReason !== void 0) {
1342
1370
  job.reason = verdictReason;
1371
+ } else if (outOfMemory !== void 0) {
1372
+ job.reason = scrubReason(outOfMemory, config);
1343
1373
  } else if (ending?.reason === void 0) {
1344
1374
  job.reason = (outcome === "ok" ? void 0 : held?.reason) ?? (isolated ? fromHook ?? job.reason : fromHook);
1345
1375
  }
@@ -1458,4 +1488,109 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
1458
1488
  return recovered;
1459
1489
  }
1460
1490
 
1461
- export { LEASE_SECONDS, RENEW_INTERVAL_SECONDS, deliverCommitHooks, describeHookEnd, execArgv, execCommandLine, finishJob, hookLimitsFor, hostCommandEnvironment, hostEnvironment, isGroupAlive, isIdle, isTimedOut, isUnfinished, jobEnv, listAdoptableJobs, listenOnBroker, microVmCommandLine, needsRenew, outcomeFromExitCode, provisionEnvironment, provisionMicroVm, provisionProcess, readCpuUsec, readJobFile, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, stopOrphanedMicroVms, unfinishedJobs, workdirProblem, writeJobFile };
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 };