@outerlayer/cli 0.19.1 → 0.22.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.
- package/CHANGELOG.md +33 -0
- package/README.md +26 -7
- package/dist/{agent-setup-AM42SA2D.js → agent-setup-XAJH6MPW.js} +13 -12
- package/dist/build-GTMEADQD.js +12 -0
- package/dist/build-command-ND2ENDBS.js +50 -0
- package/dist/build-info.json +1 -1
- package/dist/{builder-C4QNTYMS.js → builder-OAUSTZHL.js} +7 -5
- package/dist/{check-W2JGXN3B.js → check-3YHMJQ3P.js} +48 -50
- package/dist/{chunk-F2KXHIFI.js → chunk-22DYXLU7.js} +360 -66
- package/dist/{chunk-P53VEAOF.js → chunk-2CMJLYV6.js} +1 -1
- package/dist/{chunk-FBTGMSQF.js → chunk-2HT2XGJX.js} +16 -9
- package/dist/{chunk-S5PCA3SN.js → chunk-2J5JGLW5.js} +1 -1
- package/dist/{chunk-R22NZSGA.js → chunk-34XORKFD.js} +9 -9
- package/dist/{chunk-QOZKPLVJ.js → chunk-3W333NFO.js} +40 -1
- package/dist/{chunk-HOKXGFDR.js → chunk-4JHVPFH6.js} +22 -16
- package/dist/{chunk-AMFWYESY.js → chunk-4NM3I3VV.js} +1 -1
- package/dist/{chunk-PIZRPNKR.js → chunk-4QKLMNI3.js} +5 -5
- package/dist/{chunk-NNLA7LLQ.js → chunk-5OCIHFGE.js} +29 -5
- package/dist/{chunk-PSN33ERW.js → chunk-6IO62VJS.js} +3 -3
- package/dist/{chunk-7YP7T6GV.js → chunk-7V7KG3YM.js} +2 -2
- package/dist/{chunk-QNLX477Q.js → chunk-AEKSX4EY.js} +165 -72
- package/dist/{chunk-JHGEBPLE.js → chunk-ATRIVTRE.js} +3 -3
- package/dist/{chunk-X5H2WYMB.js → chunk-BLW6GPBZ.js} +1 -1
- package/dist/{chunk-MLAAT4S6.js → chunk-C3HKR67W.js} +3 -3
- package/dist/{chunk-XZ4H4X5F.js → chunk-C3OVGKVJ.js} +15 -8
- package/dist/{chunk-BM63HB2M.js → chunk-EAQSOE62.js} +2 -2
- package/dist/{chunk-LW7G5TJB.js → chunk-EXV37PKS.js} +2 -2
- package/dist/{chunk-B6SCN5NY.js → chunk-FGW2774C.js} +175 -44
- package/dist/{chunk-KYS3TQAG.js → chunk-G2WME4RG.js} +4 -4
- package/dist/{chunk-LRG3ZMZD.js → chunk-G6DZCKA6.js} +4 -4
- package/dist/chunk-GISNHSYL.js +41 -0
- package/dist/{chunk-G2YNJIGF.js → chunk-HCVLP7XA.js} +10 -1
- package/dist/{chunk-QEFKA2QD.js → chunk-I66MQDXH.js} +2 -2
- package/dist/{chunk-3CWUOFV6.js → chunk-IRBNIRQJ.js} +12 -7
- package/dist/{chunk-CG36IONK.js → chunk-JRCSET55.js} +2 -2
- package/dist/{chunk-CGGPS7SI.js → chunk-JVB63NIB.js} +5 -5
- package/dist/{chunk-M2ZWWTWZ.js → chunk-KDHYLBRR.js} +1 -1
- package/dist/{chunk-N2PXGQRL.js → chunk-KIGSGZBB.js} +1 -1
- package/dist/{chunk-EAEELQVQ.js → chunk-KOQELWQ6.js} +5 -5
- package/dist/{chunk-MTP2QEVO.js → chunk-KPBQYEND.js} +1 -1
- package/dist/{chunk-VYAL7PUE.js → chunk-LOB2SUM3.js} +1 -1
- package/dist/{chunk-GGOMT6KF.js → chunk-PHHBYAL6.js} +1 -1
- package/dist/{chunk-IF2OSI5T.js → chunk-QOALB6ZD.js} +2 -2
- package/dist/{chunk-BWSUPRDK.js → chunk-TPKYTORP.js} +1 -1
- package/dist/{chunk-XNGMQ27R.js → chunk-UD3EXMKB.js} +2 -2
- package/dist/{chunk-7UWVLZXD.js → chunk-USWVXQIH.js} +1 -1
- package/dist/{chunk-BQXFBI7T.js → chunk-V7MQKOZY.js} +32 -7
- package/dist/{chunk-6X4SQ5MY.js → chunk-V7SAQ645.js} +1 -1
- package/dist/{chunk-TWF3RICI.js → chunk-VSUXCMMH.js} +110 -11
- package/dist/{chunk-FVMYKL6L.js → chunk-VWLOQ6FI.js} +1 -1
- package/dist/{chunk-3ZR7Y23Y.js → chunk-VX74ND77.js} +650 -70
- package/dist/{chunk-Z7K3L4FC.js → chunk-W3SWJ2TD.js} +2 -2
- package/dist/chunk-WERZMIKA.js +718 -0
- package/dist/{chunk-GEMQLBMW.js → chunk-WMHYIBJW.js} +6 -6
- package/dist/{chunk-5VML45HH.js → chunk-XCRZTXRL.js} +2 -2
- package/dist/{chunk-ECODQ7II.js → chunk-ZFLOGMUC.js} +1 -1
- package/dist/chunk-ZNPE7FW5.js +137 -0
- package/dist/claude-launcher-4BQNXYAF.js +267 -0
- package/dist/{cli-EXX2R73G.js → cli-NMGVZQPB.js} +189 -119
- package/dist/{command-ZQNEPDS6.js → command-GE62AJZY.js} +32 -34
- package/dist/{config-CLSGABD5.js → config-7Q52COZ3.js} +3 -2
- package/dist/{connect-cmd-UNYKGDAL.js → connect-cmd-ZFOK6HEL.js} +5 -5
- package/dist/{context-cleanup-CHEWNYU6.js → context-cleanup-DSA3HGQU.js} +1 -1
- package/dist/{control-plane-launch-34LNRJWR.js → control-plane-launch-SZTE4TUS.js} +62 -28
- package/dist/{dist-WO7NOJNG.js → dist-7IH56ELA.js} +1 -1
- package/dist/{docker-BFU3AQGY.js → docker-2OVBKVRK.js} +7 -7
- package/dist/{doctor-DHUM5DBB.js → doctor-3SQGZVYP.js} +35 -36
- package/dist/{emit-artifact-cmd-XBHM647X.js → emit-artifact-cmd-R42BE6ID.js} +19 -18
- package/dist/{emit-cmd-W55AEBCA.js → emit-cmd-3W5IO5ET.js} +3 -3
- package/dist/{emit-criteria-cmd-CRTYEF7D.js → emit-criteria-cmd-MQZE7W4Y.js} +52 -16
- package/dist/{emit-finding-cmd-7JHVRYOP.js → emit-finding-cmd-ZS5JEGGM.js} +13 -12
- package/dist/{emit-result-cmd-5NZMLCJK.js → emit-result-cmd-FUE3DXBW.js} +19 -18
- package/dist/{emit-step-cmd-347XNGPQ.js → emit-step-cmd-T7PRA44X.js} +14 -13
- package/dist/{exec-client-7UMESIE2.js → exec-client-YIDTWXMZ.js} +4 -3
- package/dist/{governing-source-HAA4OW57.js → governing-source-TORXL4I4.js} +19 -18
- package/dist/{guest-init-KPBQOFE5.js → guest-init-TO6HAWIT.js} +4 -3
- package/dist/{hook-fast-RL5E5OXE.js → hook-fast-WFB2ZMRB.js} +2 -2
- package/dist/host-key-2LXQDSBO.js +9 -0
- package/dist/{import-capture-cmd-TK4XY7RD.js → import-capture-cmd-Q56DDZVO.js} +1 -1
- package/dist/index.js +5 -5
- package/dist/init-2CRAVIUU.js +52 -0
- package/dist/{init-V6FOMD2V.js → init-AHDZCMDC.js} +32 -33
- package/dist/{init-cmd-Z4O4PPLZ.js → init-cmd-IXDL2ZSL.js} +25 -23
- package/dist/{init-update-7DVAKWW3.js → init-update-YB3PODDH.js} +69 -24
- package/dist/{install-4SEFIIW2.js → install-CZUIW2AF.js} +5 -4
- package/dist/{install-cmd-QDOTR4OM.js → install-cmd-Q7RJGFUO.js} +6 -5
- package/dist/{login-browser-DX35MARZ.js → login-browser-BN5UKPHS.js} +3 -3
- package/dist/{logout-cmd-5V5MMBSY.js → logout-cmd-RZFWTNYD.js} +2 -2
- package/dist/{logs-AMLKC3VP.js → logs-RJPYTVQE.js} +3 -2
- package/dist/loop-ZDW6TRAA.js +75 -0
- package/dist/machine-JCOHLASP.js +47 -0
- package/dist/{mcp-install-cmd-GGWB3JJ2.js → mcp-install-cmd-GD4QUP7Z.js} +2 -2
- package/dist/{mcp-serve-cmd-N3UTY6EE.js → mcp-serve-cmd-IULQPVHL.js} +6 -5
- package/dist/{paths-7XP63OAX.js → paths-N3LU4O2Z.js} +3 -2
- package/dist/pending-GRCV3ZR4.js +9 -0
- package/dist/{pidfile-3DM4OBKO.js → pidfile-IF3TCL5A.js} +4 -3
- package/dist/receive-files-T2BIUBQL.js +254 -0
- package/dist/{record-3WP6V2K5.js → record-73PQE77B.js} +4 -3
- package/dist/relay-PSZ4LGMW.js +210 -0
- package/dist/relay-protocol-K7KP2SPI.js +6 -0
- package/dist/{runner-env-FRZFFHDF.js → runner-env-LDOBJESN.js} +6 -5
- package/dist/runner-identity-RWCBTY2A.js +14 -0
- package/dist/{runner-key-5FOWCW7J.js → runner-key-HMYLP6WN.js} +32 -33
- package/dist/{session-sender-U2BBUWNO.js → session-sender-2G25JYHI.js} +20 -19
- package/dist/{spec-cmd-FVLJOFGC.js → spec-cmd-LBVXM6XS.js} +13 -12
- package/dist/{starter-pack-6LQVLBIA.js → starter-pack-5EQYGSJF.js} +2 -2
- package/dist/{status-BINH7MBG.js → status-QZB2GWXJ.js} +36 -38
- package/dist/{statusline-fast-KN3BX67H.js → statusline-fast-2OK3DVAA.js} +2 -2
- package/dist/{sync-cmd-VXOUFS4I.js → sync-cmd-TQQIR4PN.js} +19 -18
- package/dist/{watch-OH645CV2.js → watch-4JES4PCS.js} +19 -18
- package/dist/{work-claim-cmd-ZRPSHMPT.js → work-claim-cmd-XG6O7B63.js} +17 -16
- package/dist/{work-cmd-5I3J2XQQ.js → work-cmd-L6WMPAA2.js} +13 -12
- package/dist/{work-comment-cmd-UCCPWUSR.js → work-comment-cmd-SNTDCPGH.js} +13 -12
- package/dist/{work-logs-cmd-7352CJPG.js → work-logs-cmd-3S7IAVII.js} +13 -12
- package/dist/{work-open-pr-cmd-LSH4VHJM.js → work-open-pr-cmd-E3WMO5XT.js} +15 -14
- package/dist/{work-pr-cmd-H4OQR26N.js → work-pr-cmd-22T6FG4T.js} +14 -13
- package/dist/work-stop-cmd-WR7ESS4Z.js +53 -0
- package/package.json +2 -2
- package/skill-pack/maintained/outerlayer-build/SKILL.md +347 -0
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/evidence.md +2 -2
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/release.md +4 -4
- package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-loop.md +3 -1
- package/skill-pack/shipped.json +2 -0
- package/skill-pack/template/build/SKILL.md +4 -253
- package/skill-pack/validators/acceptance-criteria.yaml +1 -1
- package/dist/build-TQ6X5PYN.js +0 -12
- package/dist/chunk-3UZTIRLF.js +0 -320
- package/dist/chunk-CX22S5BA.js +0 -45
- package/dist/chunk-LLM3I5YU.js +0 -89
- package/dist/chunk-SADUQSO4.js +0 -50
- package/dist/chunk-VMJPFQNE.js +0 -261
- package/dist/claude-launcher-J5M44PFS.js +0 -115
- package/dist/host-key-U4B5QETR.js +0 -8
- package/dist/init-XG47KCI4.js +0 -53
- package/dist/loop-TPUYUMSY.js +0 -77
- package/dist/machine-TRHBT5NE.js +0 -48
- package/dist/pending-532ABZYC.js +0 -8
- package/dist/relay-L76HDX72.js +0 -46
- package/dist/{chunk-YVZJ6GKF.js → chunk-DIB2WLBL.js} +1 -1
- package/dist/{chunk-GOSFARMS.js → chunk-F35VMF5M.js} +1 -1
- package/dist/{chunk-DSF2FWAZ.js → chunk-IZSVXRDR.js} +1 -1
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/agent-briefs.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/criteria-judge.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/references/review-briefs.md +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/criterion-citations.mjs +0 -0
- /package/skill-pack/{template/build → maintained/outerlayer-build}/scripts/record-criteria.mjs +0 -0
|
@@ -1,33 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import {
|
|
4
|
-
import { writePendingUpdate } from './chunk-MTP2QEVO.js';
|
|
5
|
-
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-FGW2774C.js';
|
|
6
4
|
import { decideInstalledSwitch } from './chunk-3JVYMD4G.js';
|
|
7
|
-
import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-YETTWTAY.js';
|
|
8
|
-
import { writeJobFile, readJobFile, readJobFileChecked } from './chunk-SADUQSO4.js';
|
|
9
5
|
import { compareVersions, satisfiesRange } from './chunk-I2UOAV7X.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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-KPBQYEND.js';
|
|
9
|
+
import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-2J5JGLW5.js';
|
|
10
|
+
import { currentSkillCommand, formerSkillProblem, placeholderQueues, placeholderProblem } from './chunk-WMHYIBJW.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-
|
|
12
|
+
import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, cleanVersions, isInLayout, stagingDir } from './chunk-4NM3I3VV.js';
|
|
14
13
|
import { RepoFilter } from './chunk-TBT347UY.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, BUILD_LABEL, RUNNER_LABEL } from './chunk-
|
|
18
|
-
import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-P53VEAOF.js';
|
|
14
|
+
import { checkFit } from './chunk-I66MQDXH.js';
|
|
15
|
+
import { LEASE_SECONDS, provisionContainer, leaseExpiresAtMs, deliverContainerHooks, lifecycleSteps, lifecycleFailureReason, prepareBranch, containerCommandExec, measureDisk, setUpRunnerCgroup, createContainerRuntime, parseCpuList, ensureTunnel, keepGhConfigCurrent, filesAreCopied } from './chunk-VX74ND77.js';
|
|
16
|
+
import { writeJobFile, ensureRunnerIdentity, readJobFile, builderName, runnerLabelValue, ensureFailureCount, builderSpec, ensureBuilder, spawnRun, BUILD_LABEL, RUNNER_LABEL, readJobFileChecked } from './chunk-WERZMIKA.js';
|
|
17
|
+
import { describeCallFailure, retrySend, shortClaim, relativeToRunner, formatDuration, BuildTokens, classifyRefusal, refusalCode, createRunnerLog, formatClock } from './chunk-C3OVGKVJ.js';
|
|
18
|
+
import { isSignatureRefusal, WorkCommandError, getJson, postJson } from './chunk-2HT2XGJX.js';
|
|
19
|
+
import { isBuiltinContainer, isBuiltinVm, isBuiltinProcess, loadRunnerConfig, checkHooks, builtinKind, DEFAULT_UPDATE_MAX_WAIT_HOURS } from './chunk-2CMJLYV6.js';
|
|
22
20
|
import { exceedsDisk } from './chunk-7RQ73DLO.js';
|
|
23
|
-
import { claimPidFile, readPidFile, releasePidFile } from './chunk-
|
|
21
|
+
import { claimPidFile, readPidFile, releasePidFile } from './chunk-ZFLOGMUC.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-
|
|
27
|
-
import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, jobsDir, pidFilePath, spoolDir,
|
|
28
|
-
import { parseClaimIdentity, parseClaimedRepositories, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-
|
|
24
|
+
import { loadHostKey, ensureHostKey, signerFor } from './chunk-KDHYLBRR.js';
|
|
25
|
+
import { RUNNER_DIR_MODE, runnerDir, buildDir, attemptStamp, jobDir, RUNNER_FILE_MODE, jobsDir, pidFilePath, spoolDir, rootfsDir } from './chunk-KIGSGZBB.js';
|
|
26
|
+
import { parseClaimIdentity, parseClaimedRepositories, parseUnreachable, repositoriesOf, primaryOf, sharedUserEnvironment, JOB_MOUNT, claudeCredential } from './chunk-V7MQKOZY.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,
|
|
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
|
|
|
@@ -85,6 +84,9 @@ var HEARTBEAT_TIMEOUT_MS = 1e4;
|
|
|
85
84
|
var ERROR_CODE_MAX = 100;
|
|
86
85
|
var ERROR_MESSAGE_MAX = 500;
|
|
87
86
|
var BLOCKED_REASON_MAX = 500;
|
|
87
|
+
var TAKES_PATTERNS_MAX = 50;
|
|
88
|
+
var TAKES_PATTERN_CHARS_MAX = 200;
|
|
89
|
+
var WAITING_ITEMS_MAX = 200;
|
|
88
90
|
var UNREACHABLE_MESSAGE = "the request did not reach the platform";
|
|
89
91
|
function noPlatformMessage(status) {
|
|
90
92
|
return `the request got HTTP ${status} with no error message from the platform`;
|
|
@@ -112,6 +114,35 @@ function workRequestFromFailure(err) {
|
|
|
112
114
|
function workRequestFromRetryFailure(outcome) {
|
|
113
115
|
return shapeWorkRequestFailure(outcome.code, outcome.status, outcome.message);
|
|
114
116
|
}
|
|
117
|
+
function stopsOf(data) {
|
|
118
|
+
const raw = data?.stops;
|
|
119
|
+
if (!Array.isArray(raw)) return [];
|
|
120
|
+
const stops = [];
|
|
121
|
+
for (const entry of raw) {
|
|
122
|
+
const stop = entry;
|
|
123
|
+
if (stop === null || typeof stop !== "object" || typeof stop.claimId !== "string") continue;
|
|
124
|
+
stops.push({
|
|
125
|
+
claimId: stop.claimId,
|
|
126
|
+
requestedByName: typeof stop.requestedByName === "string" ? stop.requestedByName : "",
|
|
127
|
+
note: typeof stop.note === "string" && stop.note !== "" ? stop.note : null
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return stops;
|
|
131
|
+
}
|
|
132
|
+
function takesBody(takes) {
|
|
133
|
+
let truncated = false;
|
|
134
|
+
const list = (patterns) => {
|
|
135
|
+
const all = (patterns ?? []).filter((pattern) => typeof pattern === "string");
|
|
136
|
+
if (all.length > TAKES_PATTERNS_MAX) truncated = true;
|
|
137
|
+
return all.slice(0, TAKES_PATTERNS_MAX).map((pattern) => {
|
|
138
|
+
if (pattern.length > TAKES_PATTERN_CHARS_MAX) truncated = true;
|
|
139
|
+
return cutForGateway(pattern, TAKES_PATTERN_CHARS_MAX);
|
|
140
|
+
}).filter((pattern) => pattern !== "");
|
|
141
|
+
};
|
|
142
|
+
const include = list(takes.repos.include);
|
|
143
|
+
const exclude = list(takes.repos.exclude);
|
|
144
|
+
return { queues: takes.queues, repos: { include, exclude }, truncated };
|
|
145
|
+
}
|
|
115
146
|
async function sendHeartbeat(args, fetchImpl) {
|
|
116
147
|
const body = {
|
|
117
148
|
host: args.host,
|
|
@@ -132,7 +163,10 @@ async function sendHeartbeat(args, fetchImpl) {
|
|
|
132
163
|
// Free disk on the runner's file system, in bytes. Sent only while held, so a host with room sends nothing about its disk.
|
|
133
164
|
lowDisk: args.lowDisk === void 0 ? void 0 : { free: args.lowDisk.free, needed: args.lowDisk.needed },
|
|
134
165
|
// Why the runner claims nothing. Sent only while it holds, so a runner that takes work says nothing about it.
|
|
135
|
-
blocked: args.blocked === void 0 ? void 0 : { reason: cutForGateway(args.blocked.reason, BLOCKED_REASON_MAX) }
|
|
166
|
+
blocked: args.blocked === void 0 ? void 0 : { reason: cutForGateway(args.blocked.reason, BLOCKED_REASON_MAX) },
|
|
167
|
+
state: args.state,
|
|
168
|
+
waitingItems: args.waitingItems === void 0 ? void 0 : args.waitingItems.slice(0, WAITING_ITEMS_MAX),
|
|
169
|
+
takes: args.takes === void 0 ? void 0 : takesBody(args.takes)
|
|
136
170
|
};
|
|
137
171
|
try {
|
|
138
172
|
const response = await postJson(
|
|
@@ -146,11 +180,22 @@ async function sendHeartbeat(args, fetchImpl) {
|
|
|
146
180
|
await signerFor(args.configPath)
|
|
147
181
|
);
|
|
148
182
|
const runnerVersion = response.data?.runnerVersion;
|
|
149
|
-
|
|
183
|
+
const stops = stopsOf(response.data);
|
|
184
|
+
return typeof runnerVersion === "string" ? { ok: true, runnerVersion, stops } : { ok: true, stops };
|
|
150
185
|
} catch (err) {
|
|
151
186
|
return { ok: false, reason: describeCallFailure(err) };
|
|
152
187
|
}
|
|
153
188
|
}
|
|
189
|
+
|
|
190
|
+
// src/runner/host-state.ts
|
|
191
|
+
function hostState(flags) {
|
|
192
|
+
if (flags.draining) return "draining";
|
|
193
|
+
if (flags.restarting) return "restarting";
|
|
194
|
+
if (flags.holdingForUpdate) return "holding-for-update";
|
|
195
|
+
if (flags.blocked) return "blocked";
|
|
196
|
+
if (flags.lowDisk) return "low-disk";
|
|
197
|
+
return "taking-work";
|
|
198
|
+
}
|
|
154
199
|
var OUTCOMES = ["updated", "rolled_back", "verification_failed", "not_published", "retrying"];
|
|
155
200
|
function lastUpdatePath(configPath) {
|
|
156
201
|
return join(runnerDir(configPath), "last-update.json");
|
|
@@ -930,6 +975,7 @@ var QUEUE_ORDER = ["amend", "implement"];
|
|
|
930
975
|
var TRACKER_REPOSITORY_HOSTS = {
|
|
931
976
|
github: "github.com"
|
|
932
977
|
};
|
|
978
|
+
var PROBE_INTERVAL_MS = 3e3;
|
|
933
979
|
var HOUSEKEEPING_INTERVAL_MS = 36e5;
|
|
934
980
|
function logOf(state) {
|
|
935
981
|
state.deps.log ??= createRunnerLog(state.deps.now ?? (() => /* @__PURE__ */ new Date()));
|
|
@@ -1042,6 +1088,7 @@ async function sendTickHeartbeat(state, workRequest) {
|
|
|
1042
1088
|
let next = workRequest;
|
|
1043
1089
|
for (; ; ) {
|
|
1044
1090
|
reportRepeating(state, "the heartbeat's previous call", void 0, "line");
|
|
1091
|
+
const waitingItems = state.draining === true ? runningItems(state) : [];
|
|
1045
1092
|
const result = await sendHeartbeat(
|
|
1046
1093
|
{
|
|
1047
1094
|
configPath: state.config.configPath,
|
|
@@ -1056,12 +1103,25 @@ async function sendTickHeartbeat(state, workRequest) {
|
|
|
1056
1103
|
workRequest: next,
|
|
1057
1104
|
lastUpdate: readLastUpdate(state.config.configPath),
|
|
1058
1105
|
lowDisk: state.lowDisk,
|
|
1059
|
-
blocked: state.blocked
|
|
1106
|
+
blocked: state.blocked,
|
|
1107
|
+
state: hostState({
|
|
1108
|
+
draining: state.draining === true,
|
|
1109
|
+
restarting: state.restarting === true,
|
|
1110
|
+
holdingForUpdate: state.holdingForUpdate === true,
|
|
1111
|
+
blocked: state.blocked !== void 0,
|
|
1112
|
+
lowDisk: state.lowDisk !== void 0
|
|
1113
|
+
}),
|
|
1114
|
+
// Left out unless a draining runner has builds to wait on, so the gateway stores null.
|
|
1115
|
+
waitingItems: waitingItems.length > 0 ? waitingItems : void 0,
|
|
1116
|
+
takes: { queues: state.config.queues, repos: { include: state.config.repos?.include, exclude: state.config.repos?.exclude } }
|
|
1060
1117
|
},
|
|
1061
1118
|
state.deps.fetchImpl
|
|
1062
1119
|
);
|
|
1063
1120
|
reportRepeating(state, "the heartbeat", result.ok ? void 0 : `heartbeat failed: ${result.reason}`);
|
|
1064
|
-
if (result.ok)
|
|
1121
|
+
if (result.ok) {
|
|
1122
|
+
considerNamedVersion(state, result.runnerVersion);
|
|
1123
|
+
applyStops(state, result.stops);
|
|
1124
|
+
}
|
|
1065
1125
|
next = state.queuedWorkRequest;
|
|
1066
1126
|
state.queuedWorkRequest = void 0;
|
|
1067
1127
|
if (next === void 0) break;
|
|
@@ -1070,6 +1130,27 @@ async function sendTickHeartbeat(state, workRequest) {
|
|
|
1070
1130
|
state.heartbeating = false;
|
|
1071
1131
|
}
|
|
1072
1132
|
}
|
|
1133
|
+
function applyStops(state, stops) {
|
|
1134
|
+
for (const stop of stops) {
|
|
1135
|
+
const held = [...state.jobs.values()].find((entry) => entry.job.claimId === stop.claimId);
|
|
1136
|
+
const active = held !== void 0 && !held.ending ? held : void 0;
|
|
1137
|
+
if (active === void 0) {
|
|
1138
|
+
reportRepeating(
|
|
1139
|
+
state,
|
|
1140
|
+
`stop of claim ${stop.claimId}`,
|
|
1141
|
+
held === void 0 ? `ignoring a stop for claim ${stop.claimId}: this runner holds no build under that claim` : `#${held.job.item} a stop was asked for; the build is already ending`,
|
|
1142
|
+
"line"
|
|
1143
|
+
);
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
const who = `stopped by ${stop.requestedByName === "" ? "someone" : stop.requestedByName} through OuterLayer`;
|
|
1147
|
+
const reason = stop.note === null ? who : `${who}: ${stop.note}`;
|
|
1148
|
+
logOf(state).line(`#${active.job.item} ${reason} \xB7 ending the build`);
|
|
1149
|
+
void endJob(state, active, "stopped", { reason }).catch((err) => {
|
|
1150
|
+
logOf(state).error(`#${active.job.item} could not be stopped: ${describeCallFailure(err)}`);
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1073
1154
|
function ownVersionOf(deps) {
|
|
1074
1155
|
return (deps.cliVersion ?? resolveVersion()).split(" ")[0] ?? "";
|
|
1075
1156
|
}
|
|
@@ -1388,7 +1469,7 @@ async function housekeepingPass(state) {
|
|
|
1388
1469
|
state.housekeepingRunning = true;
|
|
1389
1470
|
try {
|
|
1390
1471
|
state.deps.housekeeping ??= {};
|
|
1391
|
-
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) });
|
|
1392
1473
|
const result = await runHousekeeping({
|
|
1393
1474
|
configPath: state.config.configPath,
|
|
1394
1475
|
keepDays: config.jobsKeepDays,
|
|
@@ -1405,9 +1486,17 @@ async function housekeepingPass(state) {
|
|
|
1405
1486
|
}
|
|
1406
1487
|
}
|
|
1407
1488
|
async function runTick(state) {
|
|
1489
|
+
state.restarting = false;
|
|
1490
|
+
state.holdingForUpdate = false;
|
|
1408
1491
|
reloadConfig(state);
|
|
1409
|
-
if (restartOntoInstalled(state))
|
|
1410
|
-
|
|
1492
|
+
if (restartOntoInstalled(state)) {
|
|
1493
|
+
state.restarting = true;
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (await updateWhenIdle(state)) {
|
|
1497
|
+
state.holdingForUpdate = true;
|
|
1498
|
+
return;
|
|
1499
|
+
}
|
|
1411
1500
|
if (holdWhenBlocked(state)) return;
|
|
1412
1501
|
if (holdOffForDisk(state)) return;
|
|
1413
1502
|
const log = logOf(state);
|
|
@@ -1460,10 +1549,10 @@ async function runTick(state) {
|
|
|
1460
1549
|
);
|
|
1461
1550
|
}
|
|
1462
1551
|
var CLAIM_REFUSAL_CODES = new Set(
|
|
1463
|
-
"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(" ")
|
|
1464
1553
|
);
|
|
1465
1554
|
async function tryClaimAndStart(state, item, queue) {
|
|
1466
|
-
const { postJson: postJson2 } = await import('./work-cmd-
|
|
1555
|
+
const { postJson: postJson2 } = await import('./work-cmd-L6WMPAA2.js');
|
|
1467
1556
|
const log = logOf(state);
|
|
1468
1557
|
const hostKey = await loadHostKey(state.config.configPath);
|
|
1469
1558
|
const claim = await retrySend(
|
|
@@ -1567,6 +1656,9 @@ async function tryClaimAndStart(state, item, queue) {
|
|
|
1567
1656
|
return { kind: "unreadable_repositories" };
|
|
1568
1657
|
}
|
|
1569
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
|
+
}
|
|
1570
1662
|
try {
|
|
1571
1663
|
await startJob(state, item, queue, claimId, itemKey, branch, identity, repositories);
|
|
1572
1664
|
} catch (err) {
|
|
@@ -1697,7 +1789,8 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
|
|
|
1697
1789
|
aborted: () => active.ending,
|
|
1698
1790
|
deadlineMs: startedAt.getTime() + state.config.timeLimitMinutes * 6e4,
|
|
1699
1791
|
fetchImpl: state.deps.fetchImpl,
|
|
1700
|
-
now: state.deps.now
|
|
1792
|
+
now: state.deps.now,
|
|
1793
|
+
leaseExpiryMs: () => leaseExpiresAtMs(active.job)
|
|
1701
1794
|
}).catch((err) => ({ ok: false, reason: `provision failed: ${describeCallFailure(err)}`, aborted: false }));
|
|
1702
1795
|
const provisionTook = formatDuration(nowOf(state).getTime() - provisionStartedMs);
|
|
1703
1796
|
if (active.ending) return;
|
|
@@ -1710,6 +1803,11 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
|
|
|
1710
1803
|
active.job.container = provisioned.state;
|
|
1711
1804
|
active.job.environment = provisioned.environment;
|
|
1712
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
|
+
}
|
|
1713
1811
|
} else if (vmMode) {
|
|
1714
1812
|
const provisioned = await provisionMicroVm({
|
|
1715
1813
|
config: state.config,
|
|
@@ -1941,14 +2039,16 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
|
|
|
1941
2039
|
const composed = microVmCommandLine({ job, cmdline, runtime, config: state.config, cli: join(runtime.cliBuild().distDir, "index.js") });
|
|
1942
2040
|
running = runCommand(composed.line, { ...composed.env, OUTERLAYER_JOB_DIR: dirPath }, dirPath, join(dirPath, "command.log"), state.deps.spawnFn);
|
|
1943
2041
|
} else if (job.container !== void 0) {
|
|
1944
|
-
const
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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
|
+
}
|
|
1952
2052
|
} else {
|
|
1953
2053
|
const build = job.build;
|
|
1954
2054
|
const commandEnv = hostCommandEnvironment(jobEnv(state.config, item.number, itemKey, dirPath, process.env, branch, identity?.author), build, state.config, identity?.hooksPath);
|
|
@@ -1959,12 +2059,13 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
|
|
|
1959
2059
|
await endJob(state, active, "failed", { stage: "command" });
|
|
1960
2060
|
return;
|
|
1961
2061
|
}
|
|
1962
|
-
active.cmd = running;
|
|
1963
|
-
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 } };
|
|
1964
2064
|
active.job = job;
|
|
1965
2065
|
writeJobFile(jobFilePath, job);
|
|
1966
2066
|
const commandStartedMs = nowOf(state).getTime();
|
|
1967
|
-
|
|
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}`);
|
|
1968
2069
|
const sender = await startSender({
|
|
1969
2070
|
config: state.config,
|
|
1970
2071
|
job,
|
|
@@ -1976,6 +2077,10 @@ async function startJob(state, item, queue, claimId, itemKey, branch, identity,
|
|
|
1976
2077
|
active.job = job;
|
|
1977
2078
|
writeJobFile(jobFilePath, job);
|
|
1978
2079
|
}
|
|
2080
|
+
if (running === void 0) {
|
|
2081
|
+
void probeBuild(state, active);
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
1979
2084
|
running.onExit.then(({ code, signal, error }) => {
|
|
1980
2085
|
if (active.ending) return;
|
|
1981
2086
|
if (error !== void 0) {
|
|
@@ -2122,12 +2227,84 @@ async function renewLeaseIfDue(state, active, nowMs) {
|
|
|
2122
2227
|
active.job.lastRenewAt = new Date(nowMs).toISOString();
|
|
2123
2228
|
writeJobFile(join(active.dirPath, "job.json"), active.job);
|
|
2124
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();
|
|
2125
2231
|
} else {
|
|
2126
2232
|
log.error(`${tag} renew did not land \xB7 retrying in ${RENEW_INTERVAL_SECONDS / 60}m`);
|
|
2127
2233
|
}
|
|
2128
2234
|
active.lastRenewAtMs = nowMs;
|
|
2129
2235
|
return true;
|
|
2130
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
|
+
}
|
|
2131
2308
|
async function watchTick(state, active, nowMs, logMtimeMs) {
|
|
2132
2309
|
if (active.ending) {
|
|
2133
2310
|
if (active.job.stage === "checks" || active.job.stage === "judge") await renewLeaseIfDue(state, active, nowMs);
|
|
@@ -2136,6 +2313,7 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
|
|
|
2136
2313
|
const log = logOf(state);
|
|
2137
2314
|
const tag = `#${active.job.item}`;
|
|
2138
2315
|
const pid = active.job.pid;
|
|
2316
|
+
const started = pid !== void 0 || isContainerTracked(active.job);
|
|
2139
2317
|
if (pid !== void 0 && !isGroupAlive(pid)) {
|
|
2140
2318
|
if (active.cmd === null) {
|
|
2141
2319
|
const recorded = outcomeFromExitCode(active.dirPath);
|
|
@@ -2162,16 +2340,22 @@ async function watchTick(state, active, nowMs, logMtimeMs) {
|
|
|
2162
2340
|
}
|
|
2163
2341
|
if (refusal?.kind === "failed") {
|
|
2164
2342
|
log.error(`${tag} a token request was refused: ${refusal.reason} \xB7 ending job as failed`);
|
|
2165
|
-
await endJob(state, active, "failed", { stage:
|
|
2343
|
+
await endJob(state, active, "failed", { stage: started ? "command" : "provision", reason: refusal.reason });
|
|
2166
2344
|
return;
|
|
2167
2345
|
}
|
|
2168
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
|
+
}
|
|
2169
2353
|
if (isTimedOut(new Date(active.job.startedAt).getTime(), nowMs, state.config.timeLimitMinutes)) {
|
|
2170
2354
|
log.line(`${tag} past the ${state.config.timeLimitMinutes}m time limit \xB7 ending job as timed_out`);
|
|
2171
2355
|
await endJob(state, active, "timed_out");
|
|
2172
2356
|
return;
|
|
2173
2357
|
}
|
|
2174
|
-
if (
|
|
2358
|
+
if (!started) return;
|
|
2175
2359
|
if (active.job.container !== void 0) {
|
|
2176
2360
|
const limit = state.config.build?.disk ?? "40g";
|
|
2177
2361
|
const used = await measureDisk(runtimeOf(state), active.job.container);
|
|
@@ -2219,6 +2403,117 @@ async function probeCredentials(config, fetchImpl = fetch) {
|
|
|
2219
2403
|
return { ok: false, fatal: false, reason: describeCallFailure(err) };
|
|
2220
2404
|
}
|
|
2221
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
|
+
}
|
|
2222
2517
|
async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
2223
2518
|
const log = deps.log ??= createRunnerLog(deps.now ?? (() => /* @__PURE__ */ new Date()));
|
|
2224
2519
|
const update = deps.update;
|
|
@@ -2226,7 +2521,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2226
2521
|
const outcome = await onStartUnderMarker({
|
|
2227
2522
|
configPath,
|
|
2228
2523
|
own: ownVersionOf(deps),
|
|
2229
|
-
check: update.check ? () => update.check(configPath) : async () => (await import('./check-
|
|
2524
|
+
check: update.check ? () => update.check(configPath) : async () => (await import('./check-3YHMJQ3P.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl }),
|
|
2230
2525
|
now: () => (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime(),
|
|
2231
2526
|
log
|
|
2232
2527
|
});
|
|
@@ -2239,6 +2534,12 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2239
2534
|
const hookProblem = checkHooks(config);
|
|
2240
2535
|
if (hookProblem) throw new RunnerStartError(hookProblem);
|
|
2241
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
|
+
}
|
|
2242
2543
|
const probe = await probeCredentials(config, deps.fetchImpl);
|
|
2243
2544
|
if (!probe.ok && probe.fatal) throw new RunnerStartError(probe.reason);
|
|
2244
2545
|
if (!probe.ok) log.error(`the gateway did not answer at start: ${probe.reason} \xB7 starting anyway`);
|
|
@@ -2269,23 +2570,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2269
2570
|
const adopted = { job, dirPath, lastRenewAtMs: nowMs(), cmd: null, cpuSample: null, ending: false };
|
|
2270
2571
|
state.jobs.set(dirPath, adopted);
|
|
2271
2572
|
attemptLogs.open(job.item, dirPath);
|
|
2272
|
-
if (job.container !== void 0)
|
|
2273
|
-
const cores = job.container.cpuset === void 0 ? void 0 : parseCpuList(job.container.cpuset);
|
|
2274
|
-
if (cores !== void 0) runtimeOf(state).cpus.hold(job.container.buildId, cores);
|
|
2275
|
-
const tokens = buildTokensFor(state, adopted, job.item);
|
|
2276
|
-
runtimeOf(state).credentials.set(dirPath, tokens);
|
|
2277
|
-
await ensureTunnel(runtimeOf(state), job.container, dirPath, config, job.itemKey, job.branch, job.repositories);
|
|
2278
|
-
try {
|
|
2279
|
-
await tokens.token("read");
|
|
2280
|
-
keepGhConfigCurrent(
|
|
2281
|
-
tokens,
|
|
2282
|
-
job.container.buildDir,
|
|
2283
|
-
(err) => logOf(state).error(`#${job.item} the read token could not be written for gh: ${describeCallFailure(err)}`)
|
|
2284
|
-
);
|
|
2285
|
-
} catch (err) {
|
|
2286
|
-
logOf(state).error(`#${job.item} could not give the adopted build a read token: ${describeCallFailure(err)}`);
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2573
|
+
if (job.container !== void 0) await rejoinContainerBuild(state, adopted);
|
|
2289
2574
|
if (job.build !== void 0 && !runtimeOf(state).tunnels.has(job.build.buildId)) {
|
|
2290
2575
|
await startHostServices({ runtime: runtimeOf(state), config, build: job.build, jobDir: dirPath, itemKey: job.itemKey, process: job.build.workDir !== void 0 }).catch((err) => {
|
|
2291
2576
|
logOf(state).error(`the tunnel of #${job.item} could not start again: ${describeCallFailure(err)}`);
|
|
@@ -2295,6 +2580,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2295
2580
|
log.line(adoptedLine);
|
|
2296
2581
|
attemptLogs.note(job.item, adoptedLine);
|
|
2297
2582
|
}
|
|
2583
|
+
await adoptTrackedBuilds(state, jobsDir(configPath));
|
|
2298
2584
|
for (const { job, released } of await retryFailedReleases(jobsDir(configPath), config, {
|
|
2299
2585
|
fetchImpl: deps.fetchImpl,
|
|
2300
2586
|
now: deps.now,
|
|
@@ -2312,12 +2598,11 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2312
2598
|
}
|
|
2313
2599
|
let intakeClosed = false;
|
|
2314
2600
|
let closed = false;
|
|
2315
|
-
let draining = false;
|
|
2316
2601
|
let stopping = false;
|
|
2317
2602
|
let exited = false;
|
|
2318
2603
|
let interrupts = 0;
|
|
2319
2604
|
state.pollTimerSeconds = state.config.pollSeconds;
|
|
2320
|
-
|
|
2605
|
+
let pollTimer = setInterval(() => void tick(state), state.pollTimerSeconds * 1e3);
|
|
2321
2606
|
const hooksKind = builtinKind(config.hooks.provision);
|
|
2322
2607
|
const housekeepingTimer = hooksKind === "container" || hooksKind === "vm" ? (() => {
|
|
2323
2608
|
const everyMs = deps.housekeeping?.intervalMs ?? HOUSEKEEPING_INTERVAL_MS;
|
|
@@ -2337,6 +2622,9 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2337
2622
|
});
|
|
2338
2623
|
}
|
|
2339
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);
|
|
2340
2628
|
const onSigterm = () => void drain();
|
|
2341
2629
|
const onSigusr1 = () => void stopNow();
|
|
2342
2630
|
const onSigint = () => {
|
|
@@ -2357,6 +2645,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2357
2645
|
if (intakeClosed) return;
|
|
2358
2646
|
intakeClosed = true;
|
|
2359
2647
|
clearInterval(pollTimer);
|
|
2648
|
+
pollTimer = setInterval(() => void sendTickHeartbeat(state, void 0), (state.pollTimerSeconds ?? state.config.pollSeconds) * 1e3);
|
|
2360
2649
|
clearInterval(housekeepingTimer);
|
|
2361
2650
|
};
|
|
2362
2651
|
const close = () => {
|
|
@@ -2364,18 +2653,21 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2364
2653
|
detachSignals();
|
|
2365
2654
|
if (closed) return;
|
|
2366
2655
|
closed = true;
|
|
2656
|
+
clearInterval(pollTimer);
|
|
2367
2657
|
clearInterval(watchTimer);
|
|
2658
|
+
clearInterval(probeTimer);
|
|
2368
2659
|
};
|
|
2369
2660
|
const finish2 = () => {
|
|
2370
2661
|
if (exited) return;
|
|
2371
2662
|
exited = true;
|
|
2372
2663
|
close();
|
|
2664
|
+
for (const relay of state.containerRuntime?.relays.values() ?? []) void relay.close();
|
|
2373
2665
|
releasePidFile(pidFilePath(configPath), self);
|
|
2374
2666
|
log.line("runner exited");
|
|
2375
2667
|
};
|
|
2376
2668
|
const drain = async (onWaiting) => {
|
|
2377
|
-
if (draining) return;
|
|
2378
|
-
draining = true;
|
|
2669
|
+
if (state.draining === true) return;
|
|
2670
|
+
state.draining = true;
|
|
2379
2671
|
stopIntake();
|
|
2380
2672
|
let passes = 0;
|
|
2381
2673
|
while (state.jobs.size > 0 && !exited) {
|
|
@@ -2397,7 +2689,9 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
|
2397
2689
|
if (stopping) return;
|
|
2398
2690
|
stopping = true;
|
|
2399
2691
|
stopIntake();
|
|
2692
|
+
clearInterval(pollTimer);
|
|
2400
2693
|
clearInterval(watchTimer);
|
|
2694
|
+
clearInterval(probeTimer);
|
|
2401
2695
|
log.line(`stopping: ending ${state.jobs.size} running job(s) now`);
|
|
2402
2696
|
await Promise.all([...state.jobs.values()].map((active) => endJob(state, active, "stopped")));
|
|
2403
2697
|
finish2();
|
|
@@ -2483,4 +2777,4 @@ function runnerProcessState(configPath) {
|
|
|
2483
2777
|
return isSameProcess(entry.pid, entry.start) ? { state: "running", pid: entry.pid } : { state: "stale", pid: entry.pid };
|
|
2484
2778
|
}
|
|
2485
2779
|
|
|
2486
|
-
export { RunnerStartError, listJobFiles, probeCredentials, requestStop, runnerProcessState, start, tick, watchTick };
|
|
2780
|
+
export { RunnerStartError, listJobFiles, probeBuild, probeCredentials, requestStop, runnerProcessState, start, tick, watchTick };
|