@outerlayer/cli 0.26.3 → 0.27.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 (129) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +12 -9
  3. package/dist/{agent-setup-PZYQWN4H.js → agent-setup-UW36TAQM.js} +13 -13
  4. package/dist/{bridge-5KZ36YDG.js → bridge-B5VJVECY.js} +2 -2
  5. package/dist/build-TNM7HYCA.js +12 -0
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-WEGLC2F2.js → builder-LDJGS2JC.js} +6 -6
  8. package/dist/{check-WJJ325EQ.js → check-JVBAWIN6.js} +44 -43
  9. package/dist/{chunk-7LVF4E5V.js → chunk-24ID7EQ2.js} +7 -4
  10. package/dist/{chunk-HXFRJKMU.js → chunk-2F762IXY.js} +2 -2
  11. package/dist/{chunk-2K3TCQHW.js → chunk-2I3LIRZP.js} +26 -8
  12. package/dist/{chunk-UKCNOAO6.js → chunk-55GNQPO6.js} +17 -6
  13. package/dist/{chunk-XSPA5ODY.js → chunk-6BLXUD56.js} +4 -3
  14. package/dist/{chunk-MWPBF2XG.js → chunk-6KKM7VGZ.js} +32 -4
  15. package/dist/{chunk-VPEGE3QZ.js → chunk-6TRNCUM4.js} +1 -1
  16. package/dist/{chunk-MLD2NA7N.js → chunk-7AQ5AM7U.js} +12 -5
  17. package/dist/{chunk-HBISTTG3.js → chunk-7OUOLGUV.js} +20 -5
  18. package/dist/{chunk-6FAP657M.js → chunk-ABXQBEYK.js} +1 -1
  19. package/dist/{chunk-TSW4DGV2.js → chunk-B6K7R5UY.js} +2 -2
  20. package/dist/chunk-BM7EC3OZ.js +181 -0
  21. package/dist/{chunk-W3NVHOCS.js → chunk-BPKO76QR.js} +4 -4
  22. package/dist/{chunk-QRZLUUNN.js → chunk-BWH36FOC.js} +8 -3
  23. package/dist/{chunk-PYSKSROG.js → chunk-D7ECMB7B.js} +4 -4
  24. package/dist/{chunk-FO7F6QSV.js → chunk-DJHNEGNM.js} +27 -3
  25. package/dist/{chunk-TPAF24PK.js → chunk-E5IJ5533.js} +2 -2
  26. package/dist/{chunk-JWRX5ZTT.js → chunk-EPPIISRD.js} +2 -2
  27. package/dist/{chunk-6M4LNO22.js → chunk-FP23SVDD.js} +6 -6
  28. package/dist/{chunk-GOSFARMS.js → chunk-G2JWZDFA.js} +9 -2
  29. package/dist/{chunk-XFGL53GU.js → chunk-G3EZA6MK.js} +56 -8
  30. package/dist/{chunk-5PMQDQFV.js → chunk-GFUE5TKO.js} +3 -3
  31. package/dist/{chunk-XLC7KEUV.js → chunk-GLUKDQS4.js} +1 -1
  32. package/dist/{chunk-ZLEXMZFW.js → chunk-JH2GQU5T.js} +5 -5
  33. package/dist/{chunk-YVZJ6GKF.js → chunk-KT4CN6OU.js} +1 -1
  34. package/dist/{chunk-JKNYTNYD.js → chunk-LOKSLO25.js} +1 -1
  35. package/dist/{chunk-665QWBNW.js → chunk-M3MHHIUW.js} +1 -1
  36. package/dist/{chunk-4IFKLYMM.js → chunk-MATUHHN4.js} +1 -1
  37. package/dist/{chunk-MQ743JX3.js → chunk-MHX7YX7Y.js} +1 -1
  38. package/dist/{chunk-TEVO3RUV.js → chunk-N4D5TMVU.js} +4 -4
  39. package/dist/{chunk-EORWWFPA.js → chunk-O2IJTY7L.js} +2 -2
  40. package/dist/{chunk-EABW6AJQ.js → chunk-PJDQPF5N.js} +18 -5
  41. package/dist/{chunk-KDDFI5MR.js → chunk-QHH4OSD4.js} +1 -1
  42. package/dist/{chunk-W7LGHNXT.js → chunk-QIVOTQOQ.js} +5 -5
  43. package/dist/{chunk-JF572PM5.js → chunk-QKIQ2R7F.js} +3 -3
  44. package/dist/{chunk-L4CW6LZQ.js → chunk-RHAGBUL5.js} +5 -2
  45. package/dist/{chunk-TKL74NLB.js → chunk-RHGF73IU.js} +1 -1
  46. package/dist/{chunk-SKZG7SAN.js → chunk-RK7FWC6B.js} +206 -42
  47. package/dist/{chunk-6MZSX4M7.js → chunk-STSLH5EJ.js} +3 -1
  48. package/dist/{chunk-K7FCZCLQ.js → chunk-TXHJ7BGR.js} +13 -13
  49. package/dist/{chunk-364XDGOK.js → chunk-UQYBCJ2L.js} +3 -3
  50. package/dist/{chunk-2AKDF6C2.js → chunk-VS27BP3E.js} +5 -5
  51. package/dist/{chunk-J7BXZLSO.js → chunk-WSYXLLA5.js} +29 -8
  52. package/dist/{chunk-WX2WEERU.js → chunk-WUL2OP5A.js} +3 -3
  53. package/dist/{chunk-ZPXI7LMG.js → chunk-WVDP6HVZ.js} +6 -6
  54. package/dist/{chunk-MXTN35XN.js → chunk-XBPUTQ7E.js} +1 -1
  55. package/dist/{chunk-UNF7MWEL.js → chunk-XS3FDWSZ.js} +1 -1
  56. package/dist/{chunk-VJBFQP3H.js → chunk-XZ6Q6ZNR.js} +9 -5
  57. package/dist/{chunk-F6DIHHBS.js → chunk-Y4OKEFAX.js} +1 -1
  58. package/dist/{chunk-XDHYZWEK.js → chunk-Y7ZCHBYB.js} +2 -2
  59. package/dist/chunk-YB3CSYXX.js +95 -0
  60. package/dist/{chunk-VHVFU6QU.js → chunk-YF56R4DV.js} +86 -31
  61. package/dist/chunk-YJLHUKYS.js +38 -0
  62. package/dist/{chunk-XF77SNB4.js → chunk-ZXRWMOJ3.js} +287 -51
  63. package/dist/{claude-launcher-O2U6P6ZD.js → claude-launcher-TKX4WCUN.js} +8 -4
  64. package/dist/{cli-2UUJM3AE.js → cli-XVXQDKZT.js} +141 -135
  65. package/dist/{command-GQBHC53U.js → command-B2T7VR77.js} +33 -32
  66. package/dist/{config-JJ3EO7SB.js → config-TWS5VZEN.js} +3 -3
  67. package/dist/{connect-cmd-2JHSLBXK.js → connect-cmd-HGS7YPA2.js} +10 -10
  68. package/dist/{container-start-AUJHF2BC.js → container-start-JE7HTKZG.js} +32 -31
  69. package/dist/{control-plane-launch-654RV5PO.js → control-plane-launch-DBQA3KLJ.js} +9 -9
  70. package/dist/{docker-NYAIIX2F.js → docker-MJEEHUUA.js} +6 -6
  71. package/dist/{doctor-NV2PUERZ.js → doctor-6ZIF2BRZ.js} +184 -37
  72. package/dist/{emit-artifact-cmd-XXTCHP6V.js → emit-artifact-cmd-ALDK55Y2.js} +14 -14
  73. package/dist/{emit-cmd-WQH2NK7H.js → emit-cmd-VAQHRWJR.js} +4 -4
  74. package/dist/{emit-criteria-cmd-VMZIRJCQ.js → emit-criteria-cmd-Q7SM6VP2.js} +11 -11
  75. package/dist/{emit-finding-cmd-KMJNYQI7.js → emit-finding-cmd-2NBAPDSV.js} +11 -11
  76. package/dist/{emit-result-cmd-HRTXGZLK.js → emit-result-cmd-KUSL56RI.js} +14 -14
  77. package/dist/{emit-step-cmd-AUHA3EXY.js → emit-step-cmd-KD4AKEAT.js} +12 -12
  78. package/dist/{exec-client-J6L5FDRR.js → exec-client-6WVS7DRZ.js} +3 -3
  79. package/dist/{governing-source-PTJ6EAA3.js → governing-source-VVUA5TKB.js} +16 -16
  80. package/dist/{guest-init-CXIHA5VI.js → guest-init-H5K2RW3P.js} +4 -4
  81. package/dist/{hook-fast-SDFXJEAW.js → hook-fast-3SUJM765.js} +1 -1
  82. package/dist/{host-key-5TKBNUZ4.js → host-key-XNKZCWTS.js} +3 -3
  83. package/dist/{image-tag-5SJ6ZKT7.js → image-tag-R34J4O3F.js} +3 -3
  84. package/dist/index.js +4 -4
  85. package/dist/init-ETKGFCXR.js +55 -0
  86. package/dist/{init-UZWRJMPZ.js → init-Q2RPUUVP.js} +33 -32
  87. package/dist/{init-cmd-SQKWPACS.js → init-cmd-M6MQRGOU.js} +19 -18
  88. package/dist/{init-update-FJDKY4SJ.js → init-update-N4HTZGVS.js} +59 -30
  89. package/dist/{inspect-6UYAAXH7.js → inspect-IPFA7AAT.js} +1 -1
  90. package/dist/{install-QSTTBUHX.js → install-6OIY3KHZ.js} +6 -5
  91. package/dist/{install-cmd-3DXJJUAA.js → install-cmd-JMT67P35.js} +5 -5
  92. package/dist/{layout-UOJQGEPW.js → layout-XNFDTOET.js} +2 -2
  93. package/dist/{login-browser-4OMZFP3U.js → login-browser-EWXSGHM7.js} +3 -3
  94. package/dist/{logout-cmd-5V5MMBSY.js → logout-cmd-5WE2N5B5.js} +2 -2
  95. package/dist/{logs-VN2HKDSH.js → logs-XHAS6LMZ.js} +2 -2
  96. package/dist/loop-2Y4CUFNP.js +79 -0
  97. package/dist/machine-Z2S47JW7.js +50 -0
  98. package/dist/machine-skills-RRF4HLAD.js +11 -0
  99. package/dist/{mcp-install-cmd-GGWB3JJ2.js → mcp-install-cmd-LD7KYAOP.js} +2 -2
  100. package/dist/{mcp-serve-cmd-U5N5DK3H.js → mcp-serve-cmd-LY5A3CE7.js} +5 -5
  101. package/dist/{paths-ZC6BMWMT.js → paths-LHSPNORF.js} +2 -2
  102. package/dist/{pending-54HC7MM3.js → pending-3YZCSKON.js} +3 -3
  103. package/dist/{pidfile-BY5Z6YH7.js → pidfile-DKMYYXLH.js} +3 -3
  104. package/dist/{record-K23IN6QN.js → record-3LPTY3G5.js} +3 -3
  105. package/dist/{runner-env-2D7MPOIJ.js → runner-env-YRRNRKF4.js} +8 -7
  106. package/dist/{runner-identity-YYENYKDH.js → runner-identity-EX7HXPUE.js} +6 -6
  107. package/dist/{runner-key-AZVXBQIB.js → runner-key-GO277GW2.js} +32 -31
  108. package/dist/{session-sender-VH2EKAT5.js → session-sender-SC5PSU2G.js} +15 -15
  109. package/dist/{spec-cmd-BOUPLX7X.js → spec-cmd-JGYHHJ4P.js} +12 -12
  110. package/dist/{starter-pack-55EFILC5.js → starter-pack-GPU4X6B7.js} +2 -2
  111. package/dist/{status-3WC2IGVI.js → status-CJCE2FHD.js} +34 -33
  112. package/dist/{sync-cmd-GT2WTQRA.js → sync-cmd-ATXJVS7S.js} +14 -14
  113. package/dist/{watch-JED7SYAA.js → watch-M7NR3DOW.js} +14 -14
  114. package/dist/{work-claim-cmd-MY4N74HA.js → work-claim-cmd-DNUDZKJA.js} +16 -16
  115. package/dist/{work-cmd-UQ45HDM4.js → work-cmd-VECTYST6.js} +12 -12
  116. package/dist/{work-comment-cmd-I7NNSNDP.js → work-comment-cmd-F3NRWKPZ.js} +12 -12
  117. package/dist/{work-logs-cmd-YIQJVENH.js → work-logs-cmd-QF6P32FE.js} +12 -12
  118. package/dist/{work-open-pr-cmd-VP5NNTCW.js → work-open-pr-cmd-XE23JA3R.js} +13 -13
  119. package/dist/{work-pr-cmd-DKTBEFHO.js → work-pr-cmd-BH56GUKN.js} +12 -12
  120. package/dist/{work-stop-cmd-GN2WX73C.js → work-stop-cmd-C3F5HNUO.js} +12 -12
  121. package/package.json +1 -1
  122. package/skill-pack/maintained/outerlayer-cli/SKILL.md +6 -5
  123. package/skill-pack/shipped.json +1 -0
  124. package/skill-pack/template/build/SKILL.md +1 -1
  125. package/dist/build-EBQYXL7Y.js +0 -12
  126. package/dist/chunk-7RQ73DLO.js +0 -23
  127. package/dist/init-KIGYMLDM.js +0 -54
  128. package/dist/loop-OE32U2IZ.js +0 -78
  129. package/dist/machine-L7INJ4JN.js +0 -49
@@ -1,34 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, provisionEnvironment, readProvisionOut, resolveHostBuild, workdirProblem, listenOnBroker, hostEnvironment, hostCommandEnvironment, execArgv, microVmCommandLine, runCommand, commandLogOffset, execCommandLine, startSender, startHostServices, finishJob, isContainerTracked, probeCommand, appendCommandLog, judgeProbe, EXIT_CODE_FILE, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listUnreadableJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, AttemptLogBook, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, listTrackedJobs, restartSender, ALREADY_RELEASED, inTeardown } from './chunk-SKZG7SAN.js';
4
- import { writePendingUpdate } from './chunk-TKL74NLB.js';
3
+ import { releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, provisionEnvironment, readProvisionOut, resolveHostBuild, workdirProblem, listenOnBroker, hostEnvironment, hostCommandEnvironment, execArgv, MICROVM_COMMAND_DIR, commandLogOffset, startMicroVmCommand, microVmCommandLine, runCommand, execCommandLine, startSender, startHostServices, finishJob, isContainerTracked, isMicroVmTracked, probeMicroVmCommand, probeCommand, appendCommandLog, judgeProbe, EXIT_CODE_FILE, isTracked, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listUnreadableJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, AttemptLogBook, microVmOomKills, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, listTrackedJobs, restartSender, serveLease, vmAlive, writeGuestGhConfig, ALREADY_RELEASED, inTeardown } from './chunk-RK7FWC6B.js';
4
+ import { writePendingUpdate } from './chunk-RHGF73IU.js';
5
5
  import { JOB_STATE_FORMAT, readsJobState, jobStateFormatOf } from './chunk-ZVB57WI6.js';
6
- import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-6FAP657M.js';
6
+ import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-ABXQBEYK.js';
7
7
  import { decideInstalledSwitch } from './chunk-3JVYMD4G.js';
8
8
  import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-YETTWTAY.js';
9
9
  import { RUNNER_PROTOCOL } from './chunk-TPKYTORP.js';
10
- import { currentSkillCommand, formerSkillProblem, placeholderQueues, placeholderProblem } from './chunk-ZPXI7LMG.js';
11
- import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, isInLayout, cleanVersions, stagingDir } from './chunk-4IFKLYMM.js';
10
+ import { currentSkillCommand, formerSkillProblem, placeholderQueues, placeholderProblem } from './chunk-WVDP6HVZ.js';
11
+ import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, isInLayout, cleanVersions, stagingDir } from './chunk-MATUHHN4.js';
12
12
  import { freeBytes, formatBytes } from './chunk-YYYRBYWG.js';
13
13
  import { RepoFilter } from './chunk-TBT347UY.js';
14
- import { leaseTimeLeftMs, LEASE_SECONDS, provisionContainer, leaseExpiresAtMs, deliverContainerHooks, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandExec, measureDisk, setUpRunnerCgroup, createContainerRuntime, parseCpuList, ensureTunnel, keepGhConfigCurrent, filesAreCopied } from './chunk-VHVFU6QU.js';
15
- import { tagToPull, pullInstruction, imageTag, writeUpdateStatus } from './chunk-665QWBNW.js';
14
+ import { leaseTimeLeftMs, LEASE_SECONDS, provisionContainer, leaseExpiresAtMs, deliverContainerHooks, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandExec, setUpRunnerCgroup, createContainerRuntime, measureDisk, parseCpuList, ensureTunnel, keepGhConfigCurrent, filesAreCopied, gitRemoteFor, startServicesFor, createLeaseChannel } from './chunk-YF56R4DV.js';
15
+ import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-WSYXLLA5.js';
16
+ import { tagToPull, pullInstruction, imageTag, writeUpdateStatus } from './chunk-M3MHHIUW.js';
16
17
  import { isVersion, compareVersions, satisfiesRange } from './chunk-TQMI335S.js';
17
- import { checkFit } from './chunk-MLD2NA7N.js';
18
- import { claimPidFile, readPidFile, releasePidFile } from './chunk-PYSKSROG.js';
18
+ import { checkFit } from './chunk-7AQ5AM7U.js';
19
+ import { fdName } from './chunk-YB3CSYXX.js';
20
+ import { claimPidFile, writePidFile, readPidFile, releasePidFile } from './chunk-D7ECMB7B.js';
19
21
  import { startTimeOf, isSameProcess } from './chunk-JBJTC3UU.js';
20
- import { writeJobFile, ensureRunnerIdentity, readJobFile, builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, spawnRun, BUILD_LABEL, RUNNER_LABEL, readJobFileChecked } from './chunk-ZLEXMZFW.js';
21
- import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-J7BXZLSO.js';
22
- import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-UKCNOAO6.js';
23
- import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-JWRX5ZTT.js';
24
- import { exceedsDisk } from './chunk-7RQ73DLO.js';
22
+ import { writeJobFile, ensureRunnerIdentity, readJobFile, builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, spawnRun, BUILD_LABEL, RUNNER_LABEL, readJobFileChecked } from './chunk-JH2GQU5T.js';
23
+ import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-55GNQPO6.js';
24
+ import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-EPPIISRD.js';
25
+ import { exceedsDisk } from './chunk-YJLHUKYS.js';
25
26
  import { resolveVersion } from './chunk-RA3O54FC.js';
26
- import { loadHostKey, ensureHostKey, signerFor } from './chunk-F6DIHHBS.js';
27
- import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, RUNNER_FILE_MODE, jobsDir, pidFilePath, spoolDir, rootfsDir } from './chunk-L4CW6LZQ.js';
28
- import { parseClaimIdentity, parseClaimedRepositories, parseUnreachable, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-2K3TCQHW.js';
27
+ import { loadHostKey, ensureHostKey, signerFor } from './chunk-Y4OKEFAX.js';
28
+ import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, RUNNER_FILE_MODE, jobsDir, pidFilePath, spoolDir, rootfsDir } from './chunk-RHAGBUL5.js';
29
+ import { parseClaimIdentity, parseClaimedRepositories, parseUnreachable, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-2I3LIRZP.js';
29
30
  import { COMMAND_LOST_REASON, CONTAINER_STOPPED_REASON } from './chunk-JGD5TWQ2.js';
30
31
  import { randomBytes } from 'crypto';
31
- import { mkdirSync, existsSync, chmodSync, writeFileSync, renameSync, statSync, readdirSync, readFileSync, rmSync, closeSync, openSync, constants, lstatSync } from 'fs';
32
+ import { mkdirSync, existsSync, chmodSync, writeFileSync, renameSync, statSync, readdirSync, readFileSync, closeSync, rmSync, openSync, constants, lstatSync } from 'fs';
32
33
  import { join, dirname } from 'path';
33
34
  import { totalmem, constants as constants$1 } from 'os';
34
35
 
@@ -272,8 +273,11 @@ function stageReason(job) {
272
273
  }
273
274
  }
274
275
  function ownReason({ job, ending }) {
275
- if (job.microvm !== void 0) return "runs in a microVM, which does not carry on through a restart";
276
- if (!isContainerTracked(job)) return "is not a container build tracked inside its container";
276
+ if (job.microvm !== void 0) {
277
+ if (!isMicroVmTracked(job)) return "is a microVM build that a restarted runner cannot adopt";
278
+ } else if (!isContainerTracked(job)) {
279
+ return "is not a container build tracked inside its container";
280
+ }
277
281
  if (ending) return "is being ended";
278
282
  return stageReason(job);
279
283
  }
@@ -986,6 +990,30 @@ function writeCommitHooks(buildDir2, coAuthor) {
986
990
  chmodSync(data, 420);
987
991
  return dir;
988
992
  }
993
+
994
+ // src/runner/microvm/adopt.ts
995
+ var MICROVM_STOPPED_REASON = "the build's microVM stopped while no runner was managing it";
996
+ var MICROVM_SOCKETS_LOST_REASON = "the build's sockets did not survive the runner's restart";
997
+ var MICROVM_HELPER_UNREACHABLE_REASON = "the runner could not ask its helper about the build's microVM when it started";
998
+ async function judgeMicroVmAdoption(input) {
999
+ const { job, runtime } = input;
1000
+ const state = job.microvm;
1001
+ if (state === void 0 || !isMicroVmTracked(job)) return "unservable";
1002
+ const probe = await runtime.vm.helper.probe();
1003
+ if (probe.ok) {
1004
+ const unit = await probe.client.state(state.buildId).catch((err) => {
1005
+ input.onError?.(`the state of the unit of build ${state.buildId} could not be read: ${describeCallFailure(err)}`);
1006
+ return { active: true, oomKills: 0 };
1007
+ });
1008
+ if (!unit.active) return "stopped";
1009
+ } else {
1010
+ input.onError?.(`the unit of build ${state.buildId} could not be asked about, because there is no helper to ask: ${probe.detail}`);
1011
+ return probe.unreachable === true ? "unasked" : "unservable";
1012
+ }
1013
+ const inherited = runtime.vm.fdStore.inherited();
1014
+ const names = state.fdStore ?? [];
1015
+ return names.length > 0 && names.every((name) => inherited.has(name)) ? "adopt" : "unservable";
1016
+ }
989
1017
  function linuxSignalName(number) {
990
1018
  const names = {
991
1019
  1: "SIGHUP",
@@ -1749,7 +1777,7 @@ var CLAIM_REFUSAL_CODES = new Set(
1749
1777
  "pull_request_from_fork pull_request_on_default_branch pull_request_ambiguous pull_request_repository_not_connected repository_not_reachable branch_claimed_elsewhere".split(" ")
1750
1778
  );
1751
1779
  async function tryClaimAndStart(state, item, queue) {
1752
- const { postJson: postJson2 } = await import('./work-cmd-UQ45HDM4.js');
1780
+ const { postJson: postJson2 } = await import('./work-cmd-VECTYST6.js');
1753
1781
  const log = logOf(state);
1754
1782
  const hostKey = await loadHostKey(state.config.configPath);
1755
1783
  const claim = await retrySend(
@@ -1933,11 +1961,16 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
1933
1961
  writeJobFile(jobFilePath, active.job);
1934
1962
  tokens = buildTokensFor(state, active, item.number);
1935
1963
  runtimeOf(state).credentials.set(dirPath, tokens);
1964
+ let asking;
1936
1965
  try {
1937
1966
  await tokens.token("read");
1967
+ for (const entry of repositories) {
1968
+ asking = entry.repository;
1969
+ await tokens.token("read", entry.repository);
1970
+ }
1938
1971
  } catch (err) {
1939
1972
  if (active.ending) return;
1940
- await endOnTokenRefusal(state, active, err);
1973
+ await endOnTokenRefusal(state, active, err, asking);
1941
1974
  return;
1942
1975
  }
1943
1976
  if (active.ending) return;
@@ -2242,8 +2275,16 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
2242
2275
  try {
2243
2276
  if (job.microvm !== void 0) {
2244
2277
  const runtime = runtimeOf(state);
2245
- const composed = microVmCommandLine({ job, cmdline, runtime, config: state.config, cli: join(runtime.cliBuild().distDir, "index.js") });
2246
- running = runCommand(composed.line, { ...composed.env, OUTERLAYER_JOB_DIR: dirPath }, dirPath, join(dirPath, "command.log"), state.deps.spawnFn);
2278
+ if (job.microvm.jailed !== void 0 && (job.microvm.fdStore?.length ?? 0) > 0) {
2279
+ job.microvm.tracking = "microvm";
2280
+ job.microvm.commandDir = MICROVM_COMMAND_DIR;
2281
+ job.microvm.commandLogBase = commandLogOffset(dirPath, 0);
2282
+ writeJobFile(jobFilePath, job);
2283
+ await startMicroVmCommand({ job, cmdline, runtime, config: state.config });
2284
+ } else {
2285
+ const composed = microVmCommandLine({ job, cmdline, runtime, config: state.config, cli: join(runtime.cliBuild().distDir, "index.js") });
2286
+ running = runCommand(composed.line, { ...composed.env, OUTERLAYER_JOB_DIR: dirPath }, dirPath, join(dirPath, "command.log"), state.deps.spawnFn);
2287
+ }
2247
2288
  } else if (job.container !== void 0) {
2248
2289
  const runtime = runtimeOf(state);
2249
2290
  const exec = containerCommandExec({ job, cmdline, runtime, config: state.config });
@@ -2271,7 +2312,9 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
2271
2312
  writeJobFile(jobFilePath, job);
2272
2313
  const commandStartedMs = nowOf(state).getTime();
2273
2314
  active.commandStartedMs = commandStartedMs;
2274
- log.line(running === void 0 ? `${tag} command started \xB7 in container ${job.container.container}` : `${tag} command started \xB7 pid ${running.pid}`);
2315
+ log.line(
2316
+ running !== void 0 ? `${tag} command started \xB7 pid ${running.pid}` : job.container !== void 0 ? `${tag} command started \xB7 in container ${job.container.container}` : `${tag} command started \xB7 in microVM build ${job.microvm.buildId}`
2317
+ );
2275
2318
  const sender = await startSender({
2276
2319
  config: state.config,
2277
2320
  job,
@@ -2323,19 +2366,21 @@ function buildTokensFor(state, active, item) {
2323
2366
  }
2324
2367
  });
2325
2368
  }
2326
- async function endOnTokenRefusal(state, active, err) {
2369
+ async function endOnTokenRefusal(state, active, err, repository) {
2327
2370
  const log = logOf(state);
2328
2371
  const tag = `#${active.job.item}`;
2329
2372
  const refusal = classifyRefusal(err);
2373
+ const which = repository === void 0 ? "the build's read token" : `the build's read token for ${repository}`;
2330
2374
  if (refusal.kind === "retry") {
2331
- log.error(`${tag} the build's read token could not be had (${describeCallFailure(err)}) \xB7 releasing the claim to try again`);
2375
+ log.error(`${tag} ${which} could not be had (${describeCallFailure(err)}) \xB7 releasing the claim to try again`);
2332
2376
  await endJob(state, active, void 0);
2333
2377
  } else if (refusal.kind === "lease_lost") {
2334
- log.error(`${tag} the build's read token was refused: ${refusalCode(err)} \xB7 ending job as lease_lost`);
2378
+ log.error(`${tag} ${which} was refused: ${refusalCode(err)} \xB7 ending job as lease_lost`);
2335
2379
  await endJob(state, active, "lease_lost");
2336
2380
  } else {
2337
- log.error(`${tag} the build's read token was refused: ${refusal.reason} \xB7 ending job as failed`);
2338
- await endJob(state, active, "failed", { stage: "provision", reason: refusal.reason });
2381
+ const reason = repository === void 0 ? refusal.reason : `${which} could not be had: ${refusal.reason}`;
2382
+ log.error(`${tag} ${which} was refused: ${refusal.reason} \xB7 ending job as failed`);
2383
+ await endJob(state, active, "failed", { stage: "provision", reason });
2339
2384
  }
2340
2385
  }
2341
2386
  async function endOnProvisionFailure(state, active, tokens, took, reason) {
@@ -2457,19 +2502,67 @@ async function endStoppedBuild(state, active, where) {
2457
2502
  );
2458
2503
  void endJob(state, active, "interrupted", { stage: "command", reason: COMMAND_LOST_REASON });
2459
2504
  }
2505
+ var DISK_FAILURES_LIMIT = 2;
2506
+ async function checkDisk(state, active, container, tag) {
2507
+ const log = logOf(state);
2508
+ const limit = state.config.build?.disk ?? "40g";
2509
+ const runtime = runtimeOf(state);
2510
+ const reading = await measureDisk(runtime, container);
2511
+ if (active.ending) return false;
2512
+ if (exceedsDisk(reading.kib ?? 0, limit)) {
2513
+ log.line(`${tag} the build's volume holds ${reading.kib} KiB, past ${limit} \xB7 ending job as failed`);
2514
+ await endJob(state, active, "failed", { stage: "command", reason: "disk_limit_exceeded" });
2515
+ return true;
2516
+ }
2517
+ if (reading.failure === void 0) {
2518
+ active.diskFailures = 0;
2519
+ return false;
2520
+ }
2521
+ if (await runtime.host.state(container.container) !== "running" || active.ending) return false;
2522
+ active.diskFailures = (active.diskFailures ?? 0) + 1;
2523
+ if (active.diskFailures < DISK_FAILURES_LIMIT) {
2524
+ log.error(`${tag} disk check failed (${active.diskFailures} of ${DISK_FAILURES_LIMIT}): ${reading.failure} \xB7 checking again`);
2525
+ return false;
2526
+ }
2527
+ log.error(`${tag} disk check failed (${active.diskFailures} of ${DISK_FAILURES_LIMIT}): ${reading.failure} \xB7 ending job as failed`);
2528
+ await endJob(state, active, "failed", { stage: "command", reason: "disk_check_failed" });
2529
+ return true;
2530
+ }
2531
+ async function endOrWaitForMicroVm(state, active) {
2532
+ const log = logOf(state);
2533
+ const tag = `#${active.job.item}`;
2534
+ const microvm = active.job.microvm;
2535
+ const runtime = runtimeOf(state);
2536
+ const helper = await runtime.vm.helper.probe();
2537
+ const unit = helper.ok ? await helper.client.state(microvm.buildId).catch(() => void 0) : void 0;
2538
+ if (active.ending) return;
2539
+ if (unit?.active === false) {
2540
+ const killed = await microVmOomKills(runtime, microvm).catch(() => 0);
2541
+ log.line(`${tag} the build's microVM stopped \xB7 ending job as interrupted`);
2542
+ void endJob(state, active, "interrupted", killed > 0 ? { stage: "command" } : { stage: "command", reason: MICROVM_STOPPED_REASON });
2543
+ } else if (active.probeFailing !== true) {
2544
+ active.probeFailing = true;
2545
+ log.error(`${tag} the build's command could not be read from its microVM \xB7 trying again`);
2546
+ }
2547
+ }
2460
2548
  async function probeBuild(state, active) {
2461
2549
  const container = active.job.container;
2462
- if (container === void 0 || !isContainerTracked(active.job) || active.ending || active.probing) return;
2550
+ const tracked = container !== void 0 && isContainerTracked(active.job) ? container : isMicroVmTracked(active.job) ? active.job.microvm : void 0;
2551
+ if (tracked === void 0 || active.ending || active.probing) return;
2463
2552
  active.probing = true;
2464
2553
  const log = logOf(state);
2465
2554
  const tag = `#${active.job.item}`;
2466
2555
  try {
2467
2556
  const runtime = runtimeOf(state);
2468
2557
  for (; ; ) {
2469
- const offset = commandLogOffset(active.dirPath, container.commandLogBase);
2470
- const probe = await probeCommand(runtime, container, offset);
2558
+ const offset = commandLogOffset(active.dirPath, tracked.commandLogBase);
2559
+ const probe = container === void 0 ? await probeMicroVmCommand(runtime, active.job.microvm, offset) : await probeCommand(runtime, container, offset);
2471
2560
  if (active.ending) return;
2472
2561
  if (probe === void 0) {
2562
+ if (container === void 0) {
2563
+ await endOrWaitForMicroVm(state, active);
2564
+ return;
2565
+ }
2473
2566
  const where = await runtime.host.state(container.container);
2474
2567
  if (where === "stopped" || where === "missing") {
2475
2568
  await endStoppedBuild(state, active, where);
@@ -2520,7 +2613,7 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
2520
2613
  const log = logOf(state);
2521
2614
  const tag = `#${active.job.item}`;
2522
2615
  const pid = active.job.pid;
2523
- const started = pid !== void 0 || isContainerTracked(active.job);
2616
+ const started = pid !== void 0 || isTracked(active.job);
2524
2617
  if (pid !== void 0 && !isGroupAlive(pid)) {
2525
2618
  if (active.cmd === null) {
2526
2619
  const recorded = outcomeFromExitCode(active.dirPath);
@@ -2563,13 +2656,12 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
2563
2656
  return;
2564
2657
  }
2565
2658
  if (!started) return;
2566
- if (active.job.container !== void 0) {
2567
- const limit = state.config.build?.disk ?? "40g";
2568
- const used = await measureDisk(runtimeOf(state), active.job.container);
2569
- if (exceedsDisk(used ?? 0, limit)) {
2570
- log.line(`${tag} the build's volume holds ${used} KiB, past ${limit} \xB7 ending job as failed`);
2571
- await endJob(state, active, "failed", { stage: "command", reason: "disk_limit_exceeded" });
2572
- return;
2659
+ if (active.job.container !== void 0 && active.diskChecking !== true) {
2660
+ active.diskChecking = true;
2661
+ try {
2662
+ if (await checkDisk(state, active, active.job.container, tag)) return;
2663
+ } finally {
2664
+ active.diskChecking = false;
2573
2665
  }
2574
2666
  }
2575
2667
  let cpuUsecPerMinute = null;
@@ -2641,7 +2733,7 @@ async function adoptTrackedBuilds(state, jobsDirPath) {
2641
2733
  const attemptLogs = attemptLogsOf(state);
2642
2734
  const runtime = runtimeOf(state);
2643
2735
  for (const { dirPath, job, known } of listTrackedJobs(jobsDirPath)) {
2644
- if (!known) continue;
2736
+ if (!known || job.container === void 0) continue;
2645
2737
  const container = job.container;
2646
2738
  const where = await runtime.host.state(container.container).catch(() => "unknown");
2647
2739
  if (where === "stopped" || where === "missing") {
@@ -2662,6 +2754,116 @@ async function adoptTrackedBuilds(state, jobsDirPath) {
2662
2754
  void probeBuild(state, adopted);
2663
2755
  }
2664
2756
  }
2757
+ async function planMicroVmAdoption(state, jobsDirPath) {
2758
+ const plan = { adopt: [], away: [], unservable: [], unasked: [] };
2759
+ for (const { dirPath, job, known } of listTrackedJobs(jobsDirPath)) {
2760
+ if (!known || !isMicroVmTracked(job)) continue;
2761
+ const verdict = await judgeMicroVmAdoption({ job, runtime: runtimeOf(state), onError: (line) => logOf(state).error(line) });
2762
+ plan[verdict === "adopt" ? "adopt" : verdict === "stopped" ? "away" : verdict === "unasked" ? "unasked" : "unservable"].push({ dirPath, job });
2763
+ }
2764
+ return plan;
2765
+ }
2766
+ async function rejoinMicroVmBuild(state, adopted) {
2767
+ const { job, dirPath } = adopted;
2768
+ const config = state.config;
2769
+ const microvm = job.microvm;
2770
+ const runtime = runtimeOf(state);
2771
+ const tag = `#${job.item}`;
2772
+ const log = logOf(state);
2773
+ const inherited = runtime.vm.fdStore.inherited();
2774
+ const inherit = (socket) => {
2775
+ const fd = inherited.get(fdName(microvm.buildId, socket.replace(/\.sock$/, "")));
2776
+ return fd === void 0 ? void 0 : (server) => runtime.vm.fdStore.listenOn(server, fd);
2777
+ };
2778
+ const tokens = buildTokensFor(state, adopted, job.item);
2779
+ runtime.credentials.set(dirPath, tokens);
2780
+ let servicesServed = false;
2781
+ let leaseServed = false;
2782
+ try {
2783
+ const repositories = repositoriesOf({ repositories: job.repositories, repository: microvm.repository, branch: job.branch });
2784
+ const gitRemote = repositories.length === 0 ? void 0 : gitRemoteFor(runtime, { repositories, tokens, buildDir: microvm.buildDir, log: (line) => log.line(`${tag} ${line}`) });
2785
+ runtime.tunnels.set(microvm.buildId, await startServicesFor(runtime, config, microvm.buildDir, dirPath, job.itemKey, microvm.builtIns === true, gitRemote, "socket", void 0, inherit));
2786
+ servicesServed = true;
2787
+ } catch (err) {
2788
+ log.error(`${tag} the adopted build's services could not be served again: ${describeCallFailure(err)}`);
2789
+ }
2790
+ try {
2791
+ const channel = createLeaseChannel(leaseTimeLeftFor(state, adopted));
2792
+ const served = await serveLease(join(microvm.buildDir, "lease.sock"), { ...channel, accept: (stream) => runtime.leases.get(microvm.buildId)?.accept(stream) }, void 0, inherit("lease.sock"));
2793
+ runtime.leases.set(microvm.buildId, { ...channel, close: served.close });
2794
+ leaseServed = true;
2795
+ } catch (err) {
2796
+ log.error(`${tag} the adopted build's lease stream could not be served again: ${describeCallFailure(err)}`);
2797
+ }
2798
+ try {
2799
+ await tokens.token("read");
2800
+ } catch (err) {
2801
+ log.error(`${tag} could not give the adopted build a read token: ${describeCallFailure(err)}`);
2802
+ }
2803
+ let pushes = Promise.resolve();
2804
+ tokens.onRead((token) => {
2805
+ pushes = pushes.then(async () => {
2806
+ if (vmAlive(runtime, microvm)) await writeGuestGhConfig(runtime, microvm, token);
2807
+ }).catch((err) => log.error(`${tag} the read token could not be written for gh in the guest: ${describeCallFailure(err)}`));
2808
+ });
2809
+ await pushes;
2810
+ return servicesServed && leaseServed;
2811
+ }
2812
+ async function adoptTrackedMicroVms(state, plan) {
2813
+ const log = logOf(state);
2814
+ const attemptLogs = attemptLogsOf(state);
2815
+ const runtime = runtimeOf(state);
2816
+ for (const { dirPath, job } of plan.away) await finishMicroVmWhileAway(state, dirPath, job, MICROVM_STOPPED_REASON, "its unit stopped");
2817
+ for (const { dirPath, job } of plan.unservable) await finishMicroVmWhileAway(state, dirPath, job, MICROVM_SOCKETS_LOST_REASON, "the sockets it reaches the runner on are gone");
2818
+ for (const { dirPath, job } of plan.unasked) await finishMicroVmWhileAway(state, dirPath, job, MICROVM_HELPER_UNREACHABLE_REASON, "the helper did not answer about its unit");
2819
+ for (const { dirPath, job } of plan.adopt) {
2820
+ const adopted = { job, dirPath, lastRenewAtMs: 0, cmd: null, cpuSample: null, ending: false };
2821
+ state.jobs.set(dirPath, adopted);
2822
+ attemptLogs.open(job.item, dirPath);
2823
+ if (!await rejoinMicroVmBuild(state, adopted)) {
2824
+ state.jobs.delete(dirPath);
2825
+ await finishMicroVmWhileAway(state, dirPath, job, MICROVM_SOCKETS_LOST_REASON, "the sockets it reaches the runner on could not be served again");
2826
+ continue;
2827
+ }
2828
+ if (!await renewLeaseIfDue(state, adopted, nowOf(state).getTime())) continue;
2829
+ const sender = await restartSender({ config: state.config, job: adopted.job, jobDir: dirPath, deps: { spawnFn: state.deps.spawnFn, runtime, log, sleep: state.deps.sleep } });
2830
+ adopted.job = sender === void 0 ? { ...adopted.job, sender: void 0 } : { ...adopted.job, sender };
2831
+ writeJobFile(join(dirPath, "job.json"), adopted.job);
2832
+ const line = `adopted job ${job.queue} #${job.item} (claim ${shortClaim(job.claimId)}) \xB7 command tracked in microVM build ${job.microvm.buildId}`;
2833
+ log.line(line);
2834
+ attemptLogs.note(job.item, line);
2835
+ void probeBuild(state, adopted);
2836
+ }
2837
+ await removeUnadoptedSockets(state, new Set(plan.adopt.map(({ job }) => job.microvm.buildId)));
2838
+ }
2839
+ async function removeUnadoptedSockets(state, adopted) {
2840
+ const store = runtimeOf(state).vm.fdStore;
2841
+ for (const [name, fd] of store.inherited()) {
2842
+ const buildId = /^ol-([0-9a-f]+)-/.exec(name)?.[1];
2843
+ if (buildId === void 0 || adopted.has(buildId)) continue;
2844
+ await store.remove(name).catch((err) => logOf(state).error(`the descriptor ${name} could not be taken out of the store: ${describeCallFailure(err)}`));
2845
+ try {
2846
+ closeSync(fd);
2847
+ } catch {
2848
+ }
2849
+ }
2850
+ }
2851
+ async function finishMicroVmWhileAway(state, dirPath, job, reason, why) {
2852
+ const log = logOf(state);
2853
+ const recorded = outcomeFromExitCode(dirPath);
2854
+ let outcome = "interrupted";
2855
+ let ending = { reason };
2856
+ if (recorded !== void 0) {
2857
+ job.exitCode = recorded.code;
2858
+ job.commandEndReason = jobNotEndedByRunnerReason({ code: recorded.code, signal: null }, job);
2859
+ outcome = recorded.outcome === "ok" ? "ok" : "failed";
2860
+ ending = void 0;
2861
+ }
2862
+ const finished = await finishJob(job, dirPath, outcome, state.config, finishDepsOf(state), ending);
2863
+ log.line(
2864
+ `recovered job ${finished.queue} #${finished.item} (claim ${shortClaim(finished.claimId)}) as ${outcome}: ${recorded === void 0 ? `${why}, and no exit code had been read` : `its command recorded exit ${recorded.code}`}`
2865
+ );
2866
+ }
2665
2867
  async function readStoppedBuild(state, dirPath, job) {
2666
2868
  const runtime = runtimeOf(state);
2667
2869
  const container = job.container;
@@ -2721,6 +2923,11 @@ async function finishTrackedWhileAway(state, dirPath, job, where) {
2721
2923
  `recovered job ${finished.queue} #${finished.item} (claim ${shortClaim(finished.claimId)}) as ${outcome}: ${code === void 0 ? `its container ${where === "missing" ? "is gone" : "stopped"} and its volume ${read.readable ? "holds no exit code" : "could not be read"}` : `its command recorded exit ${code}`}`
2722
2924
  );
2723
2925
  }
2926
+ async function canLeaveBuilds(state) {
2927
+ if (state.deps.leave === void 0 || state.deps.image !== void 0 || !isBuiltinVm(state.config.hooks.provision)) return false;
2928
+ const runtime = runtimeOf(state);
2929
+ return (await runtime.vm.helper.probe()).ok && (await runtime.vm.fdStore.status()).ok;
2930
+ }
2724
2931
  async function logUnusableHelper(config, runtime, log) {
2725
2932
  if (!isBuiltinVm(config.hooks.provision)) return;
2726
2933
  const helper = await runtime.vm.helper.probe();
@@ -2735,7 +2942,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2735
2942
  const outcome = await onStartUnderMarker({
2736
2943
  configPath,
2737
2944
  own: ownVersionOf(deps),
2738
- check: update.check ? () => update.check(configPath) : async () => (await import('./check-WJJ325EQ.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
2945
+ check: update.check ? () => update.check(configPath) : async () => (await import('./check-JVBAWIN6.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
2739
2946
  now: () => (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime(),
2740
2947
  log
2741
2948
  });
@@ -2773,9 +2980,29 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2773
2980
  }
2774
2981
  const state = { config, jobs: /* @__PURE__ */ new Map(), deps, configMtimeMs };
2775
2982
  log.line(`runner ${config.host} started \xB7 config ${configPath} \xB7 ${describeConfig(config)}`);
2983
+ const leavesBuilds = await canLeaveBuilds(state);
2984
+ let sigusr2Target;
2985
+ let sigusr2Early = false;
2986
+ const forwardSigusr2 = () => {
2987
+ if (sigusr2Target === void 0) sigusr2Early = true;
2988
+ else sigusr2Target();
2989
+ };
2990
+ if (leavesBuilds) {
2991
+ process.on("SIGUSR2", forwardSigusr2);
2992
+ writePidFile(pidFilePath(configPath), { ...self, leaves: true });
2993
+ }
2776
2994
  const nowMs = () => deps.now ? deps.now().getTime() : Date.now();
2777
2995
  await logUnusableHelper(config, runtimeOf(state), log);
2778
- const runningMicroVms = await stopOrphanedMicroVms({ jobs: unfinishedJobs(jobsDir(configPath)), runtime: runtimeOf(state), onError: (line) => log.error(line) }).catch((err) => {
2996
+ const microVmPlan = await planMicroVmAdoption(state, jobsDir(configPath)).catch((err) => {
2997
+ log.error(`the microVM builds an earlier runner left could not be told apart: ${describeCallFailure(err)}`);
2998
+ return { adopt: [], away: [], unservable: [], unasked: [] };
2999
+ });
3000
+ const runningMicroVms = await stopOrphanedMicroVms({
3001
+ jobs: unfinishedJobs(jobsDir(configPath)),
3002
+ runtime: runtimeOf(state),
3003
+ onError: (line) => log.error(line),
3004
+ keep: new Set(microVmPlan.adopt.map(({ job }) => job.microvm.buildId))
3005
+ }).catch((err) => {
2779
3006
  log.error(`a microVM an earlier runner left could not be stopped: ${describeCallFailure(err)}`);
2780
3007
  return [];
2781
3008
  });
@@ -2799,6 +3026,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2799
3026
  attemptLogs.note(job.item, adoptedLine);
2800
3027
  }
2801
3028
  await adoptTrackedBuilds(state, jobsDir(configPath));
3029
+ await adoptTrackedMicroVms(state, microVmPlan);
2802
3030
  for (const { job, released } of await retryFailedReleases(jobsDir(configPath), config, {
2803
3031
  fetchImpl: deps.fetchImpl,
2804
3032
  now: deps.now,
@@ -2843,9 +3071,13 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2843
3071
  const probeTimer = setInterval(() => {
2844
3072
  for (const active of state.jobs.values()) void probeBuild(state, active);
2845
3073
  }, deps.probeIntervalMs ?? PROBE_INTERVAL_MS);
2846
- const onSigterm = () => deps.image === void 0 ? void drain() : leave();
3074
+ const onSigterm = () => {
3075
+ if (deps.image !== void 0) leave();
3076
+ else if (leavesBuilds && buildBlockers(runningBuilds(state)).length === 0) leave();
3077
+ else void drain();
3078
+ };
2847
3079
  const onSigusr1 = () => void stopNow();
2848
- const onSigusr2 = () => drainKeepingUp();
3080
+ const onSigusr2 = () => deps.image === void 0 ? void drain() : drainKeepingUp();
2849
3081
  const onSigint = () => {
2850
3082
  interrupts++;
2851
3083
  if (interrupts === 1) {
@@ -2859,6 +3091,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2859
3091
  process.off("SIGTERM", onSigterm);
2860
3092
  process.off("SIGUSR1", onSigusr1);
2861
3093
  process.off("SIGUSR2", onSigusr2);
3094
+ process.off("SIGUSR2", forwardSigusr2);
2862
3095
  process.off("SIGINT", onSigint);
2863
3096
  };
2864
3097
  const stopIntake = () => {
@@ -2916,7 +3149,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2916
3149
  log.line(`stopping: leaving ${state.jobs.size} running build(s) for the next runner to adopt`);
2917
3150
  state.draining = true;
2918
3151
  finish2();
2919
- deps.image?.exit?.(0);
3152
+ (deps.image?.exit ?? deps.leave?.exit)?.(0);
2920
3153
  };
2921
3154
  const stopNow = async () => {
2922
3155
  if (stopping) return;
@@ -2938,7 +3171,10 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2938
3171
  const handle = { state, drain, stopNow, close };
2939
3172
  process.on("SIGTERM", onSigterm);
2940
3173
  process.on("SIGUSR1", onSigusr1);
2941
- if (deps.image !== void 0) process.on("SIGUSR2", onSigusr2);
3174
+ if (leavesBuilds) {
3175
+ sigusr2Target = onSigusr2;
3176
+ if (sigusr2Early) onSigusr2();
3177
+ } else if (deps.image !== void 0) process.on("SIGUSR2", onSigusr2);
2942
3178
  process.on("SIGINT", onSigint);
2943
3179
  deps.onReady?.();
2944
3180
  await tick(state);
@@ -2964,7 +3200,7 @@ async function requestStop(configPath, stopNow = false, pollMs = 5e3, options =
2964
3200
  `);
2965
3201
  return;
2966
3202
  }
2967
- const signal = stopNow ? "SIGUSR1" : options.image === true ? "SIGUSR2" : "SIGTERM";
3203
+ const signal = stopNow ? "SIGUSR1" : options.image === true || entry.leaves === true ? "SIGUSR2" : "SIGTERM";
2968
3204
  try {
2969
3205
  kill(entry.pid, signal);
2970
3206
  } catch (err) {
@@ -2979,7 +3215,7 @@ async function requestStop(configPath, stopNow = false, pollMs = 5e3, options =
2979
3215
  `);
2980
3216
  return;
2981
3217
  }
2982
- if (signal === "SIGUSR2") {
3218
+ if (signal === "SIGUSR2" && entry.leaves !== true) {
2983
3219
  process.stdout.write(
2984
3220
  `sent SIGUSR2 to runner pid ${entry.pid}: it takes no more work and keeps managing its builds.
2985
3221
  The container stays up. Run \`outerlayer runner status\` to see what is left, then \`docker stop\` the container once nothing is.
@@ -2989,7 +3225,7 @@ The container stays up. Run \`outerlayer runner status\` to see what is left, th
2989
3225
  }
2990
3226
  process.stdout.write(
2991
3227
  stopNow ? `sent SIGUSR1 to runner pid ${entry.pid}, stopping now
2992
- ` : `sent SIGTERM to runner pid ${entry.pid}, draining
3228
+ ` : `sent ${signal} to runner pid ${entry.pid}, draining
2993
3229
  `
2994
3230
  );
2995
3231
  while (isSameProcess(entry.pid, entry.start)) {
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { sharedSkillNames } from './chunk-VPEGE3QZ.js';
3
+ import { sharedSkillNames } from './chunk-6TRNCUM4.js';
4
4
  import { GOVERNANCE_VARIABLE, versionAtLeast, MINIMUM_CLAUDE_VERSION, SHIM_SCRIPT, CONTROL_PLANE_DIR_VARIABLE } from './chunk-HCVLP7XA.js';
5
5
  export { MINIMUM_CLAUDE_VERSION } from './chunk-HCVLP7XA.js';
6
6
  import { findGitDirs } from './chunk-BJ3KTMDH.js';
7
- import './chunk-HBISTTG3.js';
7
+ import { setupMachineSkills } from './chunk-BM7EC3OZ.js';
8
+ import './chunk-7OUOLGUV.js';
8
9
  import './chunk-RKOTJQVG.js';
9
10
  import './chunk-QZBXLLTV.js';
10
11
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
@@ -210,12 +211,15 @@ async function runClaudeLauncher(args, deps = {}) {
210
211
  const cwd = deps.cwd ?? process.cwd();
211
212
  const env = deps.env ?? process.env;
212
213
  const stderr = deps.stderr ?? ((text) => void process.stderr.write(text));
214
+ const home = homeFromEnv(env);
215
+ const installed = setupMachineSkills(home);
216
+ for (const failure of installed.failures) stderr(`outerlayer claude: could not write a maintained skill: ${failure}
217
+ `);
213
218
  const claude = findClaude(env.PATH);
214
219
  if (claude === void 0) {
215
220
  stderr("outerlayer claude: no claude program found on PATH\n");
216
221
  return 127;
217
222
  }
218
- const home = homeFromEnv(env);
219
223
  try {
220
224
  removeStaleOwnSkillsDirs(home);
221
225
  } catch {
@@ -236,7 +240,7 @@ async function runClaudeLauncher(args, deps = {}) {
236
240
  return 1;
237
241
  }
238
242
  } else {
239
- const { resolveLaunchPlane } = await import('./control-plane-launch-654RV5PO.js');
243
+ const { resolveLaunchPlane } = await import('./control-plane-launch-DBQA3KLJ.js');
240
244
  const plane = env[GOVERNANCE_VARIABLE] === "self" ? { kind: "none" } : await resolveLaunchPlane({ cwd, env, home, fetchImpl: deps.fetchImpl, stderr });
241
245
  if (plane.kind === "none") {
242
246
  const own2 = ownSkillsDirFor(cwd, home, [], stderr);