@outerlayer/cli 0.20.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +15 -5
  3. package/dist/{agent-setup-AM42SA2D.js → agent-setup-LTSLXPS3.js} +13 -12
  4. package/dist/build-6WRNZPVR.js +12 -0
  5. package/dist/build-command-ND2ENDBS.js +50 -0
  6. package/dist/build-info.json +1 -1
  7. package/dist/{builder-C4QNTYMS.js → builder-G3MLDMTI.js} +7 -5
  8. package/dist/{check-BR5D27CX.js → check-UYKEMQNR.js} +48 -50
  9. package/dist/{chunk-AMFWYESY.js → chunk-276D7BJ7.js} +1 -1
  10. package/dist/{chunk-X5H2WYMB.js → chunk-2Q3PZ42G.js} +1 -1
  11. package/dist/{chunk-3CWUOFV6.js → chunk-3AZYLAJC.js} +13 -8
  12. package/dist/{chunk-LRG3ZMZD.js → chunk-3V5C64Y5.js} +4 -4
  13. package/dist/{chunk-QOZKPLVJ.js → chunk-3W333NFO.js} +40 -1
  14. package/dist/{chunk-7YP7T6GV.js → chunk-4AVDD7VX.js} +2 -2
  15. package/dist/{chunk-KYS3TQAG.js → chunk-56X5TOJY.js} +4 -4
  16. package/dist/{chunk-3ZR7Y23Y.js → chunk-5NJIWKD4.js} +650 -70
  17. package/dist/{chunk-NNLA7LLQ.js → chunk-5OCIHFGE.js} +29 -5
  18. package/dist/{chunk-EAEELQVQ.js → chunk-6YBLN4TR.js} +5 -5
  19. package/dist/{chunk-AD3EYI7P.js → chunk-7FEY33MY.js} +259 -56
  20. package/dist/{chunk-QNLX477Q.js → chunk-AEKSX4EY.js} +165 -72
  21. package/dist/{chunk-FBTGMSQF.js → chunk-BET347JZ.js} +16 -9
  22. package/dist/{chunk-JHGEBPLE.js → chunk-BOMRFYTZ.js} +3 -3
  23. package/dist/{chunk-MTP2QEVO.js → chunk-CQAEV3Z5.js} +1 -1
  24. package/dist/{chunk-XZ4H4X5F.js → chunk-CUFFDEF3.js} +15 -8
  25. package/dist/{chunk-BM63HB2M.js → chunk-EAQSOE62.js} +2 -2
  26. package/dist/{chunk-VYAL7PUE.js → chunk-EFB3FGLC.js} +1 -1
  27. package/dist/{chunk-GEMQLBMW.js → chunk-ENTHEUKT.js} +6 -6
  28. package/dist/{chunk-B6SCN5NY.js → chunk-FOCML5FI.js} +175 -44
  29. package/dist/{chunk-LW7G5TJB.js → chunk-FRNYCJ4W.js} +2 -2
  30. package/dist/chunk-GISNHSYL.js +41 -0
  31. package/dist/{chunk-G2YNJIGF.js → chunk-HCVLP7XA.js} +10 -1
  32. package/dist/{chunk-N2PXGQRL.js → chunk-IHNQWF6F.js} +1 -1
  33. package/dist/{chunk-CG36IONK.js → chunk-JRCSET55.js} +2 -2
  34. package/dist/{chunk-MLAAT4S6.js → chunk-JV4YGAAW.js} +3 -3
  35. package/dist/{chunk-XNGMQ27R.js → chunk-JZGFDTNC.js} +2 -2
  36. package/dist/{chunk-GGOMT6KF.js → chunk-NYTZ7HVN.js} +1 -1
  37. package/dist/{chunk-CGGPS7SI.js → chunk-O6RJ3QMU.js} +5 -5
  38. package/dist/{chunk-QEFKA2QD.js → chunk-OBCFK4QD.js} +2 -2
  39. package/dist/{chunk-HOKXGFDR.js → chunk-ORNJJ7XN.js} +22 -16
  40. package/dist/{chunk-M2ZWWTWZ.js → chunk-OW52QLK3.js} +1 -1
  41. package/dist/{chunk-PSN33ERW.js → chunk-QC72I5GQ.js} +3 -3
  42. package/dist/{chunk-FVMYKL6L.js → chunk-QF7WZUU6.js} +1 -1
  43. package/dist/{chunk-IF2OSI5T.js → chunk-QOALB6ZD.js} +2 -2
  44. package/dist/{chunk-ZX4I2MEK.js → chunk-TPKYTORP.js} +1 -1
  45. package/dist/{chunk-S5PCA3SN.js → chunk-UF5L4BAI.js} +1 -1
  46. package/dist/{chunk-R22NZSGA.js → chunk-URAKVDSI.js} +9 -9
  47. package/dist/{chunk-BQXFBI7T.js → chunk-URWYRAIC.js} +32 -7
  48. package/dist/{chunk-7UWVLZXD.js → chunk-USWVXQIH.js} +1 -1
  49. package/dist/{chunk-P53VEAOF.js → chunk-V5CG75NX.js} +1 -1
  50. package/dist/{chunk-6X4SQ5MY.js → chunk-V7SAQ645.js} +1 -1
  51. package/dist/{chunk-TWF3RICI.js → chunk-VSUXCMMH.js} +110 -11
  52. package/dist/{chunk-Z7K3L4FC.js → chunk-W3SWJ2TD.js} +2 -2
  53. package/dist/{chunk-ECODQ7II.js → chunk-XACXXKQ5.js} +1 -1
  54. package/dist/{chunk-5VML45HH.js → chunk-XCRZTXRL.js} +2 -2
  55. package/dist/chunk-XR56WEA5.js +718 -0
  56. package/dist/chunk-ZNPE7FW5.js +137 -0
  57. package/dist/{chunk-PIZRPNKR.js → chunk-ZPSLJFGL.js} +5 -5
  58. package/dist/claude-launcher-TBWV3YL3.js +267 -0
  59. package/dist/{cli-MKF7TU7X.js → cli-U253V422.js} +161 -120
  60. package/dist/{command-ZQNEPDS6.js → command-IQTPFSXY.js} +32 -34
  61. package/dist/{config-CLSGABD5.js → config-H4QS332G.js} +3 -2
  62. package/dist/{connect-cmd-UNYKGDAL.js → connect-cmd-ZFOK6HEL.js} +5 -5
  63. package/dist/{context-cleanup-CHEWNYU6.js → context-cleanup-DSA3HGQU.js} +1 -1
  64. package/dist/{control-plane-launch-34LNRJWR.js → control-plane-launch-OWSNH6YA.js} +62 -28
  65. package/dist/{dist-WO7NOJNG.js → dist-7IH56ELA.js} +1 -1
  66. package/dist/{docker-BFU3AQGY.js → docker-JU422JL7.js} +7 -7
  67. package/dist/{doctor-DHUM5DBB.js → doctor-FZTS57ZO.js} +35 -36
  68. package/dist/{emit-artifact-cmd-XBHM647X.js → emit-artifact-cmd-YLZ5WLSF.js} +19 -18
  69. package/dist/{emit-cmd-W55AEBCA.js → emit-cmd-3W5IO5ET.js} +3 -3
  70. package/dist/{emit-criteria-cmd-UNMWHOUY.js → emit-criteria-cmd-MF6TVE7C.js} +13 -12
  71. package/dist/{emit-finding-cmd-7JHVRYOP.js → emit-finding-cmd-S3YMFTZT.js} +13 -12
  72. package/dist/{emit-result-cmd-5NZMLCJK.js → emit-result-cmd-JHEUFTPX.js} +19 -18
  73. package/dist/{emit-step-cmd-347XNGPQ.js → emit-step-cmd-TUI2T3DS.js} +14 -13
  74. package/dist/{exec-client-7UMESIE2.js → exec-client-ATPF2DDB.js} +4 -3
  75. package/dist/{governing-source-HAA4OW57.js → governing-source-XSEIRSBE.js} +19 -18
  76. package/dist/{guest-init-KPBQOFE5.js → guest-init-DF7V5D2R.js} +4 -3
  77. package/dist/{hook-fast-RL5E5OXE.js → hook-fast-O3UUN6SO.js} +2 -2
  78. package/dist/host-key-ATB23GI3.js +9 -0
  79. package/dist/{import-capture-cmd-TK4XY7RD.js → import-capture-cmd-Q56DDZVO.js} +1 -1
  80. package/dist/index.js +5 -5
  81. package/dist/init-VZE72CR4.js +52 -0
  82. package/dist/{init-V6FOMD2V.js → init-ZZZPYGUH.js} +32 -33
  83. package/dist/{init-cmd-Z4O4PPLZ.js → init-cmd-W5VV62ZN.js} +25 -23
  84. package/dist/{init-update-7DVAKWW3.js → init-update-YFPIQS5G.js} +69 -24
  85. package/dist/{install-4SEFIIW2.js → install-SFACZU7E.js} +5 -4
  86. package/dist/{install-cmd-QDOTR4OM.js → install-cmd-6HTP7I43.js} +6 -5
  87. package/dist/{login-browser-DX35MARZ.js → login-browser-BN5UKPHS.js} +3 -3
  88. package/dist/{logout-cmd-5V5MMBSY.js → logout-cmd-RZFWTNYD.js} +2 -2
  89. package/dist/{logs-AMLKC3VP.js → logs-N3ZX7HFV.js} +3 -2
  90. package/dist/loop-6WENRS5S.js +75 -0
  91. package/dist/machine-M4MQBV5A.js +47 -0
  92. package/dist/{mcp-install-cmd-GGWB3JJ2.js → mcp-install-cmd-GD4QUP7Z.js} +2 -2
  93. package/dist/{mcp-serve-cmd-N3UTY6EE.js → mcp-serve-cmd-SBW7HYKD.js} +6 -5
  94. package/dist/{paths-7XP63OAX.js → paths-AKX2OOLF.js} +3 -2
  95. package/dist/pending-7SGWZCEH.js +9 -0
  96. package/dist/{pidfile-3DM4OBKO.js → pidfile-G2J5WBQS.js} +4 -3
  97. package/dist/receive-files-T2BIUBQL.js +254 -0
  98. package/dist/{record-3WP6V2K5.js → record-Q4IEYIVV.js} +4 -3
  99. package/dist/relay-PSZ4LGMW.js +210 -0
  100. package/dist/relay-protocol-K7KP2SPI.js +6 -0
  101. package/dist/{runner-env-FRZFFHDF.js → runner-env-GZHXM27A.js} +6 -5
  102. package/dist/runner-identity-FTHZFMGY.js +14 -0
  103. package/dist/{runner-key-5FOWCW7J.js → runner-key-QT7GKGEE.js} +32 -33
  104. package/dist/{session-sender-U2BBUWNO.js → session-sender-BK6TGNVO.js} +20 -19
  105. package/dist/{spec-cmd-FVLJOFGC.js → spec-cmd-ZNJ3GXWC.js} +13 -12
  106. package/dist/{starter-pack-6LQVLBIA.js → starter-pack-5EQYGSJF.js} +2 -2
  107. package/dist/{status-BINH7MBG.js → status-JOO7MGIC.js} +36 -38
  108. package/dist/{statusline-fast-KN3BX67H.js → statusline-fast-2OK3DVAA.js} +2 -2
  109. package/dist/{sync-cmd-VXOUFS4I.js → sync-cmd-W2FPY3T5.js} +19 -18
  110. package/dist/{watch-OH645CV2.js → watch-WLH5ZUZU.js} +19 -18
  111. package/dist/{work-claim-cmd-DRQCHCOK.js → work-claim-cmd-4PLDTEZR.js} +17 -16
  112. package/dist/{work-cmd-5I3J2XQQ.js → work-cmd-KWHAYUYB.js} +13 -12
  113. package/dist/{work-comment-cmd-UCCPWUSR.js → work-comment-cmd-4CUI7ZDE.js} +13 -12
  114. package/dist/{work-logs-cmd-7352CJPG.js → work-logs-cmd-AF253WLM.js} +13 -12
  115. package/dist/{work-open-pr-cmd-LSH4VHJM.js → work-open-pr-cmd-JETE6HRL.js} +15 -14
  116. package/dist/{work-pr-cmd-H4OQR26N.js → work-pr-cmd-V5G4CPMG.js} +14 -13
  117. package/dist/{work-stop-cmd-CRDHXQVW.js → work-stop-cmd-FIEKUDGE.js} +13 -12
  118. package/package.json +2 -2
  119. package/skill-pack/maintained/outerlayer-amend/SKILL.md +17 -6
  120. package/skill-pack/maintained/outerlayer-build/SKILL.md +347 -0
  121. package/skill-pack/{template/build → maintained/outerlayer-build}/references/evidence.md +2 -2
  122. package/skill-pack/{template/build → maintained/outerlayer-build}/references/release.md +4 -4
  123. package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-loop.md +3 -1
  124. package/skill-pack/shipped.json +2 -0
  125. package/skill-pack/template/build/SKILL.md +4 -253
  126. package/skill-pack/validators/acceptance-criteria.yaml +1 -1
  127. package/dist/build-TQ6X5PYN.js +0 -12
  128. package/dist/chunk-3UZTIRLF.js +0 -320
  129. package/dist/chunk-CX22S5BA.js +0 -45
  130. package/dist/chunk-LLM3I5YU.js +0 -89
  131. package/dist/chunk-SADUQSO4.js +0 -50
  132. package/dist/chunk-VMJPFQNE.js +0 -261
  133. package/dist/claude-launcher-J5M44PFS.js +0 -115
  134. package/dist/host-key-U4B5QETR.js +0 -8
  135. package/dist/init-XG47KCI4.js +0 -53
  136. package/dist/loop-EAZQMAE2.js +0 -77
  137. package/dist/machine-TRHBT5NE.js +0 -48
  138. package/dist/pending-532ABZYC.js +0 -8
  139. package/dist/relay-L76HDX72.js +0 -46
  140. package/dist/{chunk-YVZJ6GKF.js → chunk-DIB2WLBL.js} +1 -1
  141. package/dist/{chunk-GOSFARMS.js → chunk-F35VMF5M.js} +1 -1
  142. package/dist/{chunk-DSF2FWAZ.js → chunk-IZSVXRDR.js} +1 -1
  143. /package/skill-pack/{template/build → maintained/outerlayer-build}/references/agent-briefs.md +0 -0
  144. /package/skill-pack/{template/build → maintained/outerlayer-build}/references/criteria-judge.md +0 -0
  145. /package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-briefs.md +0 -0
  146. /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/criterion-citations.mjs +0 -0
  147. /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/record-criteria.mjs +0 -0
@@ -8,8 +8,10 @@ import { fileURLToPath } from 'url';
8
8
 
9
9
  createRequire(import.meta.url);
10
10
  var TEMPLATE_SKILLS = ["spec", "writing-specs", "writing-tests", "build"];
11
- var MAINTAINED_SKILLS = ["outerlayer-evidence", "outerlayer-amend", "outerlayer-findings", "outerlayer-cli"];
12
- var TEMPLATE_CLASS_LINE = "<!-- yours to edit. Copied once from the default template by outerlayer init; never overwritten. -->";
11
+ var MAINTAINED_SKILLS = ["outerlayer-evidence", "outerlayer-amend", "outerlayer-findings", "outerlayer-cli", "outerlayer-build"];
12
+ function templateClassLine(version) {
13
+ return `<!-- yours to edit. Copied once from the default template by outerlayer init ${version}; never overwritten. -->`;
14
+ }
13
15
  function maintainedClassLine(version) {
14
16
  return `<!-- maintained by OuterLayer ${version}. Edits are overwritten on the next outerlayer context emit. -->`;
15
17
  }
@@ -20,7 +22,7 @@ function skillDir(skill) {
20
22
  return `.outerlayer/skills/${skill}`;
21
23
  }
22
24
  function renderSkillFile(skillClass, content, version) {
23
- const line = skillClass === "maintained" ? maintainedClassLine(version) : TEMPLATE_CLASS_LINE;
25
+ const line = skillClass === "maintained" ? maintainedClassLine(version) : templateClassLine(version);
24
26
  const { frontmatterInner, body } = splitRawFile(content);
25
27
  if (frontmatterInner === null) return `${line}
26
28
  ${content}`;
@@ -77,7 +79,29 @@ function writeMaintainedFiles(repoRoot, files) {
77
79
  return { created, rewritten };
78
80
  }
79
81
  function maintainedFilesForExistingSkills(repoRoot, files) {
80
- return files.filter((file) => existsSync(join(repoRoot, skillDir(file.skill))) || formerNameExists(repoRoot, file.skill));
82
+ return files.filter(
83
+ (file) => existsSync(join(repoRoot, skillDir(file.skill))) || formerNameExists(repoRoot, file.skill) || requiredByBuild(repoRoot, file.skill)
84
+ );
85
+ }
86
+ function namesCore(text) {
87
+ return text.includes("outerlayer-build");
88
+ }
89
+ function requiredByBuild(repoRoot, skill) {
90
+ if (skill !== "outerlayer-build") return false;
91
+ const named = (file) => {
92
+ try {
93
+ return namesCore(readFileSync(file, "utf8"));
94
+ } catch {
95
+ return false;
96
+ }
97
+ };
98
+ if (named(join(repoRoot, skillDir("build"), "SKILL.md"))) return true;
99
+ const dir = join(repoRoot, ".outerlayer", "validators");
100
+ try {
101
+ return readdirSync(dir).some((name) => name.endsWith(".yaml") && named(join(dir, name)));
102
+ } catch {
103
+ return false;
104
+ }
81
105
  }
82
106
  var FORMER_MAINTAINED_NAMES = {
83
107
  amend: "outerlayer-amend",
@@ -97,4 +121,4 @@ function formerNameExists(repoRoot, skill) {
97
121
  return Object.entries(FORMER_MAINTAINED_NAMES).some(([former, current]) => current === skill && isMaintainedCopy(repoRoot, former));
98
122
  }
99
123
 
100
- export { FORMER_MAINTAINED_NAMES, MAINTAINED_SKILLS, TEMPLATE_SKILLS, isMaintainedCopy, loadMaintainedFiles, loadTemplateFiles, maintainedFilesForExistingSkills, resolvePackRoot, skillDir, writeMaintainedFiles };
124
+ export { FORMER_MAINTAINED_NAMES, MAINTAINED_SKILLS, TEMPLATE_SKILLS, isMaintainedCopy, loadMaintainedFiles, loadTemplateFiles, maintainedFilesForExistingSkills, namesCore, resolvePackRoot, skillDir, writeMaintainedFiles };
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { removeRetiredSkills } from './chunk-7UWVLZXD.js';
4
- import { defaultFetch } from './chunk-FVMYKL6L.js';
3
+ import { removeRetiredSkills } from './chunk-USWVXQIH.js';
4
+ import { defaultFetch } from './chunk-QF7WZUU6.js';
5
5
  import { resolveGatewayCredentials } from './chunk-NTRCK4NH.js';
6
6
  import { appendContextProvenance } from './chunk-ZJSNLVCE.js';
7
- import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-NNLA7LLQ.js';
7
+ import { loadMaintainedFiles, writeMaintainedFiles } from './chunk-5OCIHFGE.js';
8
8
  import { cliInvocation } from './chunk-CRB3POP4.js';
9
9
  import { writeDegraded, writeSourceCache, SOURCE_CACHE_TTL_RETRY_MS, writeContextManifest, SOURCE_CACHE_TTL_SUCCESS_MS, readSourceCache, isSourceCacheFresh, clearDegraded } from './chunk-YQR6E6OV.js';
10
10
  import { normalizeRepoName, parseOuterlayerConfig, classifyTree, emitTree } from './chunk-WFSUUUAN.js';
11
11
  import { homeFromEnv } from './chunk-VNVZDWO3.js';
12
- import { withTokenHeader } from './chunk-BQXFBI7T.js';
12
+ import { withTokenHeader } from './chunk-URWYRAIC.js';
13
13
  import { execFileSync } from 'child_process';
14
14
  import { existsSync, mkdirSync, writeFileSync, chmodSync, lstatSync, readFileSync, copyFileSync } from 'fs';
15
15
  import { isAbsolute, join, dirname } from 'path';
@@ -437,7 +437,7 @@ function ensureBareCache(git, home, repository, host) {
437
437
  }
438
438
  return dir;
439
439
  }
440
- var MATERIALIZED_ROOTS = ["AGENTS.md", ".claude", ".outerlayer"];
440
+ var MATERIALIZED_ROOTS = ["AGENTS.md", ".claude", ".outerlayer", ".agents"];
441
441
  function listMaterializedFiles(git, cacheRepoDir, sha) {
442
442
  const result = git(["ls-tree", "-r", "-z", sha, "--", ...MATERIALIZED_ROOTS], { cwd: cacheRepoDir, timeoutMs: 1e4 });
443
443
  if (!result.ok) return [];
@@ -1,33 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { LEASE_SECONDS, releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, provisionEnvironment, readProvisionOut, resolveHostBuild, workdirProblem, listenOnBroker, hostEnvironment, hostCommandEnvironment, execArgv, microVmCommandLine, runCommand, execCommandLine, startSender, startHostServices, finishJob, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, AttemptLogBook, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, ALREADY_RELEASED } from './chunk-B6SCN5NY.js';
4
- import { writeJobFile, readJobFile, readJobFileChecked } from './chunk-SADUQSO4.js';
5
- import { writePendingUpdate } from './chunk-MTP2QEVO.js';
6
- import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-S5PCA3SN.js';
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, listAdoptableJobs, retryFailedReleases, recoverLostJobs, hookLimitsFor, AttemptLogBook, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, listTrackedJobs, restartSender, ALREADY_RELEASED } from './chunk-FOCML5FI.js';
7
4
  import { decideInstalledSwitch } from './chunk-3JVYMD4G.js';
8
- import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-YETTWTAY.js';
9
5
  import { compareVersions, satisfiesRange } from './chunk-I2UOAV7X.js';
10
- import { RUNNER_PROTOCOL } from './chunk-ZX4I2MEK.js';
11
- import { currentSkillCommand, formerSkillProblem, placeholderQueues, placeholderProblem } from './chunk-GEMQLBMW.js';
6
+ import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-YETTWTAY.js';
7
+ import { RUNNER_PROTOCOL } from './chunk-TPKYTORP.js';
8
+ import { writePendingUpdate } from './chunk-CQAEV3Z5.js';
9
+ import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-UF5L4BAI.js';
10
+ import { currentSkillCommand, formerSkillProblem, placeholderQueues, placeholderProblem } from './chunk-ENTHEUKT.js';
12
11
  import { freeBytes, formatBytes } from './chunk-YYYRBYWG.js';
13
- import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, cleanVersions, isInLayout, stagingDir } from './chunk-AMFWYESY.js';
12
+ import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, cleanVersions, isInLayout, stagingDir } from './chunk-276D7BJ7.js';
14
13
  import { RepoFilter } from './chunk-TBT347UY.js';
15
- import { provisionContainer, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandLine, measureDisk, setUpRunnerCgroup, parseCpuList, ensureTunnel, keepGhConfigCurrent, createContainerRuntime } from './chunk-3ZR7Y23Y.js';
16
- import { spawnRun } from './chunk-VMJPFQNE.js';
17
- import { builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, BUILD_LABEL, RUNNER_LABEL } from './chunk-3UZTIRLF.js';
18
- import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-XZ4H4X5F.js';
19
- import { checkFit } from './chunk-QEFKA2QD.js';
20
- import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-FBTGMSQF.js';
21
- import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-P53VEAOF.js';
14
+ import { checkFit } from './chunk-OBCFK4QD.js';
15
+ import { LEASE_SECONDS, provisionContainer, leaseExpiresAtMs, deliverContainerHooks, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandExec, measureDisk, setUpRunnerCgroup, createContainerRuntime, parseCpuList, ensureTunnel, keepGhConfigCurrent, filesAreCopied } from './chunk-5NJIWKD4.js';
16
+ import { writeJobFile, ensureRunnerIdentity, readJobFile, builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, spawnRun, BUILD_LABEL, RUNNER_LABEL, readJobFileChecked } from './chunk-XR56WEA5.js';
17
+ import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-CUFFDEF3.js';
18
+ import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-BET347JZ.js';
19
+ import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-V5CG75NX.js';
22
20
  import { exceedsDisk } from './chunk-7RQ73DLO.js';
23
- import { claimPidFile, readPidFile, releasePidFile } from './chunk-ECODQ7II.js';
21
+ import { claimPidFile, readPidFile, releasePidFile } from './chunk-XACXXKQ5.js';
24
22
  import { startTimeOf, isSameProcess } from './chunk-JBJTC3UU.js';
25
23
  import { resolveVersion } from './chunk-RA3O54FC.js';
26
- import { loadHostKey, ensureHostKey, signerFor } from './chunk-M2ZWWTWZ.js';
27
- import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, jobsDir, pidFilePath, spoolDir, RUNNER_FILE_MODE, rootfsDir } from './chunk-N2PXGQRL.js';
28
- import { parseClaimIdentity, parseClaimedRepositories, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-BQXFBI7T.js';
24
+ import { loadHostKey, ensureHostKey, signerFor } from './chunk-OW52QLK3.js';
25
+ import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, RUNNER_FILE_MODE, jobsDir, pidFilePath, spoolDir, rootfsDir } from './chunk-IHNQWF6F.js';
26
+ import { parseClaimIdentity, parseClaimedRepositories, parseUnreachable, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-URWYRAIC.js';
27
+ import { COMMAND_LOST_REASON, CONTAINER_STOPPED_REASON } from './chunk-GISNHSYL.js';
29
28
  import { randomBytes } from 'crypto';
30
- import { mkdirSync, existsSync, chmodSync, statSync, readdirSync, writeFileSync, readFileSync, rmSync, renameSync, closeSync, openSync, constants, lstatSync } from 'fs';
29
+ import { mkdirSync, existsSync, chmodSync, writeFileSync, renameSync, statSync, readdirSync, readFileSync, rmSync, closeSync, openSync, constants, lstatSync } from 'fs';
31
30
  import { join, dirname } from 'path';
32
31
  import { totalmem, constants as constants$1 } from 'os';
33
32
 
@@ -976,6 +975,7 @@ var QUEUE_ORDER = ["amend", "implement"];
976
975
  var TRACKER_REPOSITORY_HOSTS = {
977
976
  github: "github.com"
978
977
  };
978
+ var PROBE_INTERVAL_MS = 3e3;
979
979
  var HOUSEKEEPING_INTERVAL_MS = 36e5;
980
980
  function logOf(state) {
981
981
  state.deps.log ??= createRunnerLog(state.deps.now ?? (() => /* @__PURE__ */ new Date()));
@@ -1469,7 +1469,7 @@ async function housekeepingPass(state) {
1469
1469
  state.housekeepingRunning = true;
1470
1470
  try {
1471
1471
  state.deps.housekeeping ??= {};
1472
- state.deps.housekeeping.docker ??= createHousekeepingDocker({ runnerId: runnerLabelValue(state.config.configPath), builder: builderName(state.config.configPath) });
1472
+ state.deps.housekeeping.docker ??= createHousekeepingDocker({ run: state.deps.housekeeping.run, runnerId: runnerLabelValue(state.config.configPath), builder: builderName(state.config.configPath) });
1473
1473
  const result = await runHousekeeping({
1474
1474
  configPath: state.config.configPath,
1475
1475
  keepDays: config.jobsKeepDays,
@@ -1549,10 +1549,10 @@ async function runTick(state) {
1549
1549
  );
1550
1550
  }
1551
1551
  var CLAIM_REFUSAL_CODES = new Set(
1552
- "pull_request_from_fork pull_request_on_default_branch pull_request_ambiguous pull_request_repository_not_connected branch_claimed_elsewhere".split(" ")
1552
+ "pull_request_from_fork pull_request_on_default_branch pull_request_ambiguous pull_request_repository_not_connected repository_not_reachable branch_claimed_elsewhere".split(" ")
1553
1553
  );
1554
1554
  async function tryClaimAndStart(state, item, queue) {
1555
- const { postJson: postJson2 } = await import('./work-cmd-5I3J2XQQ.js');
1555
+ const { postJson: postJson2 } = await import('./work-cmd-KWHAYUYB.js');
1556
1556
  const log = logOf(state);
1557
1557
  const hostKey = await loadHostKey(state.config.configPath);
1558
1558
  const claim = await retrySend(
@@ -1656,6 +1656,9 @@ async function tryClaimAndStart(state, item, queue) {
1656
1656
  return { kind: "unreadable_repositories" };
1657
1657
  }
1658
1658
  reportRepeating(state, "the repositories", void 0);
1659
+ for (const entry of parseUnreachable(data.unreachable)) {
1660
+ log.line(`#${item.number}: ${entry.repository} is left out of this build because its GitHub App installation is suspended`);
1661
+ }
1659
1662
  try {
1660
1663
  await startJob(state, item, queue, claimId, itemKey, branch, identity, repositories);
1661
1664
  } catch (err) {
@@ -1786,7 +1789,8 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
1786
1789
  aborted: () => active.ending,
1787
1790
  deadlineMs: startedAt.getTime() + state.config.timeLimitMinutes * 6e4,
1788
1791
  fetchImpl: state.deps.fetchImpl,
1789
- now: state.deps.now
1792
+ now: state.deps.now,
1793
+ leaseExpiryMs: () => leaseExpiresAtMs(active.job)
1790
1794
  }).catch((err) => ({ ok: false, reason: `provision failed: ${describeCallFailure(err)}`, aborted: false }));
1791
1795
  const provisionTook = formatDuration(nowOf(state).getTime() - provisionStartedMs);
1792
1796
  if (active.ending) return;
@@ -1799,6 +1803,11 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
1799
1803
  active.job.container = provisioned.state;
1800
1804
  active.job.environment = provisioned.environment;
1801
1805
  installCommitHooks(active.job, log, tag);
1806
+ const hooksDelivered = await deliverContainerHooks(runtimeOf(state), provisioned.state, dirPath);
1807
+ if (!hooksDelivered.ok) {
1808
+ await endOnProvisionFailure(state, active, tokens, provisionTook, hooksDelivered.reason);
1809
+ return;
1810
+ }
1802
1811
  } else if (vmMode) {
1803
1812
  const provisioned = await provisionMicroVm({
1804
1813
  config: state.config,
@@ -2030,14 +2039,16 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
2030
2039
  const composed = microVmCommandLine({ job, cmdline, runtime, config: state.config, cli: join(runtime.cliBuild().distDir, "index.js") });
2031
2040
  running = runCommand(composed.line, { ...composed.env, OUTERLAYER_JOB_DIR: dirPath }, dirPath, join(dirPath, "command.log"), state.deps.spawnFn);
2032
2041
  } else if (job.container !== void 0) {
2033
- const composed = containerCommandLine({ job, cmdline, runtime: runtimeOf(state), config: state.config });
2034
- running = runCommand(
2035
- composed.line,
2036
- { ...composed.env, OUTERLAYER_JOB_DIR: dirPath },
2037
- dirPath,
2038
- join(dirPath, "command.log"),
2039
- state.deps.spawnFn
2040
- );
2042
+ const runtime = runtimeOf(state);
2043
+ const exec = containerCommandExec({ job, cmdline, runtime, config: state.config });
2044
+ job.container.tracking = "container";
2045
+ job.container.commandLogBase = commandLogOffset(dirPath, 0);
2046
+ writeJobFile(jobFilePath, job);
2047
+ const started = await runtime.host.execDetached(exec);
2048
+ if (started.status !== 0) {
2049
+ const said = started.stderr.trim().split("\n")[0] ?? "";
2050
+ throw new Error(`docker exec exited ${started.status}${said === "" ? "" : `: ${said}`}`);
2051
+ }
2041
2052
  } else {
2042
2053
  const build = job.build;
2043
2054
  const commandEnv = hostCommandEnvironment(jobEnv(state.config, item.number, itemKey, dirPath, process.env, branch, identity?.author), build, state.config, identity?.hooksPath);
@@ -2048,12 +2059,13 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
2048
2059
  await endJob(state, active, "failed", { stage: "command" });
2049
2060
  return;
2050
2061
  }
2051
- active.cmd = running;
2052
- job = { ...job, stage: "command", pid: running.pid, start: running.start ?? void 0 };
2062
+ active.cmd = running ?? null;
2063
+ job = { ...job, stage: "command", ...running === void 0 ? {} : { pid: running.pid, start: running.start ?? void 0 } };
2053
2064
  active.job = job;
2054
2065
  writeJobFile(jobFilePath, job);
2055
2066
  const commandStartedMs = nowOf(state).getTime();
2056
- log.line(`${tag} command started \xB7 pid ${running.pid}`);
2067
+ active.commandStartedMs = commandStartedMs;
2068
+ log.line(running === void 0 ? `${tag} command started \xB7 in container ${job.container.container}` : `${tag} command started \xB7 pid ${running.pid}`);
2057
2069
  const sender = await startSender({
2058
2070
  config: state.config,
2059
2071
  job,
@@ -2065,6 +2077,10 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
2065
2077
  active.job = job;
2066
2078
  writeJobFile(jobFilePath, job);
2067
2079
  }
2080
+ if (running === void 0) {
2081
+ void probeBuild(state, active);
2082
+ return;
2083
+ }
2068
2084
  running.onExit.then(({ code, signal, error }) => {
2069
2085
  if (active.ending) return;
2070
2086
  if (error !== void 0) {
@@ -2211,12 +2227,84 @@ async function renewLeaseIfDue(state, active, nowMs) {
2211
2227
  active.job.lastRenewAt = new Date(nowMs).toISOString();
2212
2228
  writeJobFile(join(active.dirPath, "job.json"), active.job);
2213
2229
  log.line(`${tag} lease renewed \xB7 expires ${formatClock(new Date(nowMs + LEASE_SECONDS * 1e3), new Date(nowMs))}`);
2230
+ if (active.job.container !== void 0) state.containerRuntime?.leases.get(active.job.container.buildId)?.publish();
2214
2231
  } else {
2215
2232
  log.error(`${tag} renew did not land \xB7 retrying in ${RENEW_INTERVAL_SECONDS / 60}m`);
2216
2233
  }
2217
2234
  active.lastRenewAtMs = nowMs;
2218
2235
  return true;
2219
2236
  }
2237
+ var COMMAND_START_GRACE_MS = 3e4;
2238
+ async function endStoppedBuild(state, active, where) {
2239
+ const log = logOf(state);
2240
+ const tag = `#${active.job.item}`;
2241
+ const read = where === "stopped" ? await readStoppedBuild(state, active.dirPath, active.job) : { code: void 0, readable: true };
2242
+ if (active.ending) return;
2243
+ active.job.container.stopped = true;
2244
+ if (read.code !== void 0) {
2245
+ recordCommandExit(state, active, read.code);
2246
+ return;
2247
+ }
2248
+ log.line(
2249
+ `${tag} the build's container ${where === "missing" ? "is gone" : "stopped"} and its volume ${read.readable ? "holds no exit code" : "could not be read"} \xB7 ending job as interrupted`
2250
+ );
2251
+ void endJob(state, active, "interrupted", { stage: "command", reason: COMMAND_LOST_REASON });
2252
+ }
2253
+ async function probeBuild(state, active) {
2254
+ const container = active.job.container;
2255
+ if (container === void 0 || !isContainerTracked(active.job) || active.ending || active.probing) return;
2256
+ active.probing = true;
2257
+ const log = logOf(state);
2258
+ const tag = `#${active.job.item}`;
2259
+ try {
2260
+ const runtime = runtimeOf(state);
2261
+ for (; ; ) {
2262
+ const offset = commandLogOffset(active.dirPath, container.commandLogBase);
2263
+ const probe = await probeCommand(runtime, container, offset);
2264
+ if (active.ending) return;
2265
+ if (probe === void 0) {
2266
+ const where = await runtime.host.state(container.container);
2267
+ if (where === "stopped" || where === "missing") {
2268
+ await endStoppedBuild(state, active, where);
2269
+ } else if (active.probeFailing !== true) {
2270
+ active.probeFailing = true;
2271
+ log.error(`${tag} the build's command could not be read from its container (${where}) \xB7 trying again`);
2272
+ }
2273
+ return;
2274
+ }
2275
+ active.probeFailing = false;
2276
+ appendCommandLog(active.dirPath, probe.bytes);
2277
+ if (probe.bytes.length > 0 && offset + probe.bytes.length < probe.size) continue;
2278
+ const starting = active.commandStartedMs !== void 0 && nowOf(state).getTime() - active.commandStartedMs < COMMAND_START_GRACE_MS;
2279
+ const verdict = starting && probe.exitCode === void 0 && !probe.alive ? { kind: "running" } : judgeProbe(probe, active.commandMissed === true);
2280
+ active.commandMissed = verdict.kind === "recheck";
2281
+ if (verdict.kind === "exited") {
2282
+ recordCommandExit(state, active, verdict.code);
2283
+ } else if (verdict.kind === "lost") {
2284
+ log.line(`${tag} command gone, no exit code recorded \xB7 ending job as interrupted`);
2285
+ void endJob(state, active, "interrupted", { stage: "command", reason: COMMAND_LOST_REASON });
2286
+ }
2287
+ return;
2288
+ }
2289
+ } catch (err) {
2290
+ log.error(`${tag} reading the build's command failed: ${describeCallFailure(err)}`);
2291
+ } finally {
2292
+ active.probing = false;
2293
+ }
2294
+ }
2295
+ function recordCommandExit(state, active, code) {
2296
+ const log = logOf(state);
2297
+ const tag = `#${active.job.item}`;
2298
+ const file = join(active.dirPath, EXIT_CODE_FILE);
2299
+ writeFileSync(`${file}.partial`, `${code}
2300
+ `, { mode: RUNNER_FILE_MODE });
2301
+ renameSync(`${file}.partial`, file);
2302
+ const took = active.commandStartedMs === void 0 ? "" : ` (${formatDuration(nowOf(state).getTime() - active.commandStartedMs)})`;
2303
+ log.line(`${tag} command exited ${code}${took}`);
2304
+ active.job.exitCode = code;
2305
+ active.job.commandEndReason = jobNotEndedByRunnerReason({ code, signal: null }, active.job);
2306
+ void endJob(state, active, code === 0 ? "ok" : "failed");
2307
+ }
2220
2308
  async function watchTick(state, active, nowMs, logMtimeMs) {
2221
2309
  if (active.ending) {
2222
2310
  if (active.job.stage === "checks" || active.job.stage === "judge") await renewLeaseIfDue(state, active, nowMs);
@@ -2225,6 +2313,7 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
2225
2313
  const log = logOf(state);
2226
2314
  const tag = `#${active.job.item}`;
2227
2315
  const pid = active.job.pid;
2316
+ const started = pid !== void 0 || isContainerTracked(active.job);
2228
2317
  if (pid !== void 0 && !isGroupAlive(pid)) {
2229
2318
  if (active.cmd === null) {
2230
2319
  const recorded = outcomeFromExitCode(active.dirPath);
@@ -2251,16 +2340,22 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
2251
2340
  }
2252
2341
  if (refusal?.kind === "failed") {
2253
2342
  log.error(`${tag} a token request was refused: ${refusal.reason} \xB7 ending job as failed`);
2254
- await endJob(state, active, "failed", { stage: pid === void 0 ? "provision" : "command", reason: refusal.reason });
2343
+ await endJob(state, active, "failed", { stage: started ? "command" : "provision", reason: refusal.reason });
2255
2344
  return;
2256
2345
  }
2257
2346
  }
2347
+ const unreachable = active.job.container === void 0 ? void 0 : runtimeOf(state).relayFatal.get(active.job.container.buildId);
2348
+ if (unreachable !== void 0) {
2349
+ log.error(`${tag} ${unreachable} \xB7 ending job as failed`);
2350
+ await endJob(state, active, "failed", { stage: started ? "command" : "provision", reason: unreachable });
2351
+ return;
2352
+ }
2258
2353
  if (isTimedOut(new Date(active.job.startedAt).getTime(), nowMs, state.config.timeLimitMinutes)) {
2259
2354
  log.line(`${tag} past the ${state.config.timeLimitMinutes}m time limit \xB7 ending job as timed_out`);
2260
2355
  await endJob(state, active, "timed_out");
2261
2356
  return;
2262
2357
  }
2263
- if (pid === void 0) return;
2358
+ if (!started) return;
2264
2359
  if (active.job.container !== void 0) {
2265
2360
  const limit = state.config.build?.disk ?? "40g";
2266
2361
  const used = await measureDisk(runtimeOf(state), active.job.container);
@@ -2308,6 +2403,117 @@ async function probeCredentials(config, fetchImpl = fetch) {
2308
2403
  return { ok: false, fatal: false, reason: describeCallFailure(err) };
2309
2404
  }
2310
2405
  }
2406
+ async function rejoinContainerBuild(state, adopted) {
2407
+ const { job, dirPath } = adopted;
2408
+ const config = state.config;
2409
+ const container = job.container;
2410
+ const cores = container.cpuset === void 0 ? void 0 : parseCpuList(container.cpuset);
2411
+ if (cores !== void 0) runtimeOf(state).cpus.hold(container.buildId, cores);
2412
+ const tokens = buildTokensFor(state, adopted, job.item);
2413
+ runtimeOf(state).credentials.set(dirPath, tokens);
2414
+ await ensureTunnel(runtimeOf(state), container, dirPath, config, job.itemKey, job.branch, job.repositories, (line) => logOf(state).line(`#${job.item} ${line}`), () => leaseExpiresAtMs(adopted.job));
2415
+ try {
2416
+ await tokens.token("read");
2417
+ keepGhConfigCurrent(
2418
+ tokens,
2419
+ container.buildDir,
2420
+ (err) => logOf(state).error(`#${job.item} the read token could not be written for gh: ${describeCallFailure(err)}`),
2421
+ filesAreCopied(container) ? {
2422
+ runtime: runtimeOf(state),
2423
+ state: container,
2424
+ secrets: () => tokens.issuedTokens(),
2425
+ onFailure: (reason) => logOf(state).error(`#${job.item} ${reason}`)
2426
+ } : void 0
2427
+ );
2428
+ } catch (err) {
2429
+ logOf(state).error(`#${job.item} could not give the adopted build a read token: ${describeCallFailure(err)}`);
2430
+ }
2431
+ }
2432
+ async function adoptTrackedBuilds(state, jobsDirPath) {
2433
+ const log = logOf(state);
2434
+ const attemptLogs = attemptLogsOf(state);
2435
+ const runtime = runtimeOf(state);
2436
+ for (const { dirPath, job, known } of listTrackedJobs(jobsDirPath)) {
2437
+ if (!known) continue;
2438
+ const container = job.container;
2439
+ const where = await runtime.host.state(container.container).catch(() => "unknown");
2440
+ if (where === "stopped" || where === "missing") {
2441
+ await finishTrackedWhileAway(state, dirPath, job, where);
2442
+ continue;
2443
+ }
2444
+ const adopted = { job, dirPath, lastRenewAtMs: 0, cmd: null, cpuSample: null, ending: false };
2445
+ state.jobs.set(dirPath, adopted);
2446
+ attemptLogs.open(job.item, dirPath);
2447
+ await rejoinContainerBuild(state, adopted);
2448
+ if (!await renewLeaseIfDue(state, adopted, nowOf(state).getTime())) continue;
2449
+ const sender = await restartSender({ config: state.config, job: adopted.job, jobDir: dirPath, deps: { spawnFn: state.deps.spawnFn, runtime, log, sleep: state.deps.sleep } });
2450
+ adopted.job = sender === void 0 ? { ...adopted.job, sender: void 0 } : { ...adopted.job, sender };
2451
+ writeJobFile(join(dirPath, "job.json"), adopted.job);
2452
+ const line = `adopted job ${job.queue} #${job.item} (claim ${shortClaim(job.claimId)}) \xB7 command tracked in container ${container.container}`;
2453
+ log.line(line);
2454
+ attemptLogs.note(job.item, line);
2455
+ void probeBuild(state, adopted);
2456
+ }
2457
+ }
2458
+ async function readStoppedBuild(state, dirPath, job) {
2459
+ const runtime = runtimeOf(state);
2460
+ const container = job.container;
2461
+ runtime.leases.get(container.buildId)?.stop();
2462
+ try {
2463
+ for (let attempt = 1; attempt <= STOPPED_READ_ATTEMPTS; attempt++) {
2464
+ const started = await runtime.host.start(container.container);
2465
+ if (started.status === 0) {
2466
+ const code = await readVolumeExitCode(runtime, dirPath, container);
2467
+ if (code !== "unreadable") {
2468
+ await runtime.host.stop(container.container);
2469
+ return { code, readable: true };
2470
+ }
2471
+ await runtime.host.stop(container.container).catch(() => void 0);
2472
+ }
2473
+ if (attempt < STOPPED_READ_ATTEMPTS) await (state.deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))))(STOPPED_READ_RETRY_MS);
2474
+ }
2475
+ logOf(state).error(`#${job.item} the stopped build's volume could not be read after ${STOPPED_READ_ATTEMPTS} tries`);
2476
+ return { code: void 0, readable: false };
2477
+ } catch (err) {
2478
+ logOf(state).error(`#${job.item} the stopped build's volume could not be read: ${describeCallFailure(err)}`);
2479
+ return { code: void 0, readable: false };
2480
+ }
2481
+ }
2482
+ var STOPPED_READ_ATTEMPTS = 3;
2483
+ var STOPPED_READ_RETRY_MS = 1e3;
2484
+ async function readVolumeExitCode(runtime, dirPath, container) {
2485
+ for (; ; ) {
2486
+ const offset = commandLogOffset(dirPath, container.commandLogBase);
2487
+ const probe = await probeCommand(runtime, container, offset);
2488
+ if (probe === void 0) return "unreadable";
2489
+ appendCommandLog(dirPath, probe.bytes);
2490
+ if (probe.bytes.length > 0 && offset + probe.bytes.length < probe.size) continue;
2491
+ return probe.exitCode;
2492
+ }
2493
+ }
2494
+ async function finishTrackedWhileAway(state, dirPath, job, where) {
2495
+ const log = logOf(state);
2496
+ const container = job.container;
2497
+ const read = where === "stopped" ? await readStoppedBuild(state, dirPath, job) : { code: void 0, readable: true };
2498
+ const code = read.code;
2499
+ container.stopped = true;
2500
+ let outcome = "interrupted";
2501
+ let ending = { reason: CONTAINER_STOPPED_REASON };
2502
+ if (code !== void 0) {
2503
+ const file = join(dirPath, EXIT_CODE_FILE);
2504
+ writeFileSync(`${file}.partial`, `${code}
2505
+ `, { mode: RUNNER_FILE_MODE });
2506
+ renameSync(`${file}.partial`, file);
2507
+ job.exitCode = code;
2508
+ job.commandEndReason = jobNotEndedByRunnerReason({ code, signal: null }, job);
2509
+ outcome = code === 0 ? "ok" : "failed";
2510
+ ending = void 0;
2511
+ }
2512
+ const finished = await finishJob(job, dirPath, outcome, state.config, finishDepsOf(state), ending);
2513
+ log.line(
2514
+ `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}`}`
2515
+ );
2516
+ }
2311
2517
  async function start(configPath, deps = { fetchImpl: fetch }) {
2312
2518
  const log = deps.log ??= createRunnerLog(deps.now ?? (() => /* @__PURE__ */ new Date()));
2313
2519
  const update = deps.update;
@@ -2315,7 +2521,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2315
2521
  const outcome = await onStartUnderMarker({
2316
2522
  configPath,
2317
2523
  own: ownVersionOf(deps),
2318
- check: update.check ? () => update.check(configPath) : async () => (await import('./check-BR5D27CX.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
2524
+ check: update.check ? () => update.check(configPath) : async () => (await import('./check-UYKEMQNR.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
2319
2525
  now: () => (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime(),
2320
2526
  log
2321
2527
  });
@@ -2328,6 +2534,12 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2328
2534
  const hookProblem = checkHooks(config);
2329
2535
  if (hookProblem) throw new RunnerStartError(hookProblem);
2330
2536
  await ensureHostKey(configPath);
2537
+ if (builtinKind(config.hooks.provision) === "container" || builtinKind(config.hooks.provision) === "vm") {
2538
+ await ensureRunnerIdentity(configPath, {
2539
+ docker: deps.runnerIdentity ?? (deps.container?.host === void 0 ? void 0 : { holdsObjects: async () => false }),
2540
+ log: (line) => log.error(line)
2541
+ }).catch((err) => log.error(`the runner's Docker identity could not be stored: ${describeCallFailure(err)} \xB7 using the names its folder's path gives`));
2542
+ }
2331
2543
  const probe = await probeCredentials(config, deps.fetchImpl);
2332
2544
  if (!probe.ok && probe.fatal) throw new RunnerStartError(probe.reason);
2333
2545
  if (!probe.ok) log.error(`the gateway did not answer at start: ${probe.reason} \xB7 starting anyway`);
@@ -2358,23 +2570,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2358
2570
  const adopted = { job, dirPath, lastRenewAtMs: nowMs(), cmd: null, cpuSample: null, ending: false };
2359
2571
  state.jobs.set(dirPath, adopted);
2360
2572
  attemptLogs.open(job.item, dirPath);
2361
- if (job.container !== void 0) {
2362
- const cores = job.container.cpuset === void 0 ? void 0 : parseCpuList(job.container.cpuset);
2363
- if (cores !== void 0) runtimeOf(state).cpus.hold(job.container.buildId, cores);
2364
- const tokens = buildTokensFor(state, adopted, job.item);
2365
- runtimeOf(state).credentials.set(dirPath, tokens);
2366
- await ensureTunnel(runtimeOf(state), job.container, dirPath, config, job.itemKey, job.branch, job.repositories);
2367
- try {
2368
- await tokens.token("read");
2369
- keepGhConfigCurrent(
2370
- tokens,
2371
- job.container.buildDir,
2372
- (err) => logOf(state).error(`#${job.item} the read token could not be written for gh: ${describeCallFailure(err)}`)
2373
- );
2374
- } catch (err) {
2375
- logOf(state).error(`#${job.item} could not give the adopted build a read token: ${describeCallFailure(err)}`);
2376
- }
2377
- }
2573
+ if (job.container !== void 0) await rejoinContainerBuild(state, adopted);
2378
2574
  if (job.build !== void 0 && !runtimeOf(state).tunnels.has(job.build.buildId)) {
2379
2575
  await startHostServices({ runtime: runtimeOf(state), config, build: job.build, jobDir: dirPath, itemKey: job.itemKey, process: job.build.workDir !== void 0 }).catch((err) => {
2380
2576
  logOf(state).error(`the tunnel of #${job.item} could not start again: ${describeCallFailure(err)}`);
@@ -2384,6 +2580,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2384
2580
  log.line(adoptedLine);
2385
2581
  attemptLogs.note(job.item, adoptedLine);
2386
2582
  }
2583
+ await adoptTrackedBuilds(state, jobsDir(configPath));
2387
2584
  for (const { job, released } of await retryFailedReleases(jobsDir(configPath), config, {
2388
2585
  fetchImpl: deps.fetchImpl,
2389
2586
  now: deps.now,
@@ -2425,6 +2622,9 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2425
2622
  });
2426
2623
  }
2427
2624
  }, deps.watchIntervalMs ?? 3e4);
2625
+ const probeTimer = setInterval(() => {
2626
+ for (const active of state.jobs.values()) void probeBuild(state, active);
2627
+ }, deps.probeIntervalMs ?? PROBE_INTERVAL_MS);
2428
2628
  const onSigterm = () => void drain();
2429
2629
  const onSigusr1 = () => void stopNow();
2430
2630
  const onSigint = () => {
@@ -2455,11 +2655,13 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2455
2655
  closed = true;
2456
2656
  clearInterval(pollTimer);
2457
2657
  clearInterval(watchTimer);
2658
+ clearInterval(probeTimer);
2458
2659
  };
2459
2660
  const finish2 = () => {
2460
2661
  if (exited) return;
2461
2662
  exited = true;
2462
2663
  close();
2664
+ for (const relay of state.containerRuntime?.relays.values() ?? []) void relay.close();
2463
2665
  releasePidFile(pidFilePath(configPath), self);
2464
2666
  log.line("runner exited");
2465
2667
  };
@@ -2489,6 +2691,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
2489
2691
  stopIntake();
2490
2692
  clearInterval(pollTimer);
2491
2693
  clearInterval(watchTimer);
2694
+ clearInterval(probeTimer);
2492
2695
  log.line(`stopping: ending ${state.jobs.size} running job(s) now`);
2493
2696
  await Promise.all([...state.jobs.values()].map((active) => endJob(state, active, "stopped")));
2494
2697
  finish2();
@@ -2574,4 +2777,4 @@ function runnerProcessState(configPath) {
2574
2777
  return isSameProcess(entry.pid, entry.start) ? { state: "running", pid: entry.pid } : { state: "stale", pid: entry.pid };
2575
2778
  }
2576
2779
 
2577
- export { RunnerStartError, listJobFiles, probeCredentials, requestStop, runnerProcessState, start, tick, watchTick };
2780
+ export { RunnerStartError, listJobFiles, probeBuild, probeCredentials, requestStop, runnerProcessState, start, tick, watchTick };