@outerlayer/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +9 -0
- package/dist/{agent-setup-IFH3FB5X.js → agent-setup-USZ2SAXC.js} +2 -2
- package/dist/build-info.json +1 -1
- package/dist/{check-AXCXDYO2.js → check-CCVPBN7A.js} +26 -14
- package/dist/{chunk-5Y3QQRUH.js → chunk-3DD6AOWC.js} +37 -0
- package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
- package/dist/{chunk-L24G4PLP.js → chunk-43J3M6KB.js} +486 -21
- package/dist/{chunk-FC7TMRNA.js → chunk-4NLKY4WR.js} +2 -2
- package/dist/{lima-XN65D7GN.js → chunk-77MM5RYD.js} +0 -1
- package/dist/{chunk-D2KWEAN6.js → chunk-7DBI6SNT.js} +1 -1
- package/dist/{chunk-QDQEUVUF.js → chunk-AIW6GLET.js} +1 -1
- package/dist/{chunk-EX6M5JPR.js → chunk-ANK2B36Y.js} +131 -15
- package/dist/{chunk-RMAZ2NWA.js → chunk-BNMCXPG7.js} +14 -2
- package/dist/{chunk-UFFXNXLQ.js → chunk-FA2BOX4O.js} +31 -29
- package/dist/chunk-FBTE2A2X.js +38 -0
- package/dist/{chunk-DVDEBNQJ.js → chunk-GQD22KKN.js} +17 -6
- package/dist/{chunk-BCJNHMZT.js → chunk-HHJDN6AU.js} +18 -3
- package/dist/{chunk-JLDZTG3A.js → chunk-IASWYRVG.js} +1 -1
- package/dist/{install-cmd-7WXOIFDO.js → chunk-JWOQNNN2.js} +0 -1
- package/dist/{chunk-ZLB7GCTY.js → chunk-MIGEDH5P.js} +19 -11
- package/dist/{chunk-OHFDWLJP.js → chunk-MXB5VB4Y.js} +18 -6
- package/dist/chunk-NQXFAGHU.js +211 -0
- package/dist/chunk-OUUINEYB.js +27 -0
- package/dist/{chunk-OR6AEEDO.js → chunk-PMAMOSTP.js} +3 -3
- package/dist/{chunk-RJ467PDQ.js → chunk-RBOMWNOG.js} +1 -1
- package/dist/{chunk-4NMBDRBS.js → chunk-RZSF5B46.js} +289 -130
- package/dist/chunk-T6OIJQRT.js +31 -0
- package/dist/chunk-UGP76IZ5.js +34 -0
- package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
- package/dist/{chunk-RWOTHXYE.js → chunk-VMJ3MMBX.js} +206 -71
- package/dist/chunk-YYYRBYWG.js +32 -0
- package/dist/chunk-ZILGTTY6.js +75 -0
- package/dist/{cli-NNWPFP5S.js → cli-7WCNPBYC.js} +147 -69
- package/dist/command-ARFK3OYF.js +162 -0
- package/dist/{config-XVJYZ4IQ.js → config-HL2AIR3K.js} +1 -1
- package/dist/{connect-cmd-OJBFHZWP.js → connect-cmd-PCESZLTR.js} +4 -4
- package/dist/{context-adopt-UMA4O5NY.js → context-adopt-G5JAELE7.js} +2 -2
- package/dist/{context-materialize-G2FUFC72.js → context-materialize-3HLPZAF6.js} +2 -2
- package/dist/{dist-WDV7U7UJ.js → dist-RDLP4GGJ.js} +1 -1
- package/dist/{doctor-J3GIMR7K.js → doctor-VOMYOHQU.js} +47 -13
- package/dist/{emit-artifact-cmd-FDWYKURA.js → emit-artifact-cmd-TBF46VXZ.js} +6 -7
- package/dist/{emit-criteria-cmd-OCXHLK34.js → emit-criteria-cmd-3427XKFW.js} +3 -3
- package/dist/{emit-finding-cmd-POW7PKIA.js → emit-finding-cmd-GSI75K36.js} +3 -3
- package/dist/{emit-result-cmd-ID35SH53.js → emit-result-cmd-3PRQKB55.js} +6 -7
- package/dist/{hook-fast-EYENPK6F.js → hook-fast-NJBBZQ5Q.js} +1 -1
- package/dist/index.js +3 -2
- package/dist/{init-CZX7ZDWF.js → init-OHYFIKZD.js} +27 -56
- package/dist/{init-3ZQLFPNC.js → init-WVR4BZQ4.js} +8 -8
- package/dist/{init-cmd-W5NOLQPX.js → init-cmd-HMFNIFPI.js} +7 -7
- package/dist/inspect-XAUSB7L3.js +63 -0
- package/dist/install-DSUZE2TJ.js +13 -0
- package/dist/install-cmd-453C7IZT.js +8 -0
- package/dist/lima-7NPCNGWT.js +6 -0
- package/dist/{loop-VGM772I7.js → loop-UAJB34KA.js} +26 -11
- package/dist/{machine-5WEJA36F.js → machine-2JOWGRSZ.js} +7 -7
- package/dist/record-C43U4PKS.js +7 -0
- package/dist/session-sender-ED26V45B.js +28 -0
- package/dist/{status-KBDVUYQ3.js → status-6WNXX2O7.js} +42 -10
- package/dist/{sync-cmd-SWQ5KLPL.js → sync-cmd-YONVGUYS.js} +6 -7
- package/dist/{watch-3YW6Z3DQ.js → watch-HHKAF26L.js} +6 -7
- package/dist/{work-claim-cmd-BQPPURBW.js → work-claim-cmd-LXBZTMJA.js} +6 -6
- package/dist/{work-cmd-NUR4CAG6.js → work-cmd-OVNF44FY.js} +3 -3
- package/dist/{work-comment-cmd-6TTTYGHM.js → work-comment-cmd-6DGQNTAH.js} +3 -3
- package/dist/{work-launch-YI4CJDAP.js → work-launch-R5HTAP6X.js} +1 -1
- package/dist/{work-open-pr-cmd-KZ7QQFLD.js → work-open-pr-cmd-FCZHFHZS.js} +4 -4
- package/dist/{work-pr-cmd-QNUBE6IQ.js → work-pr-cmd-QJJVHZDC.js} +2 -2
- package/package.json +2 -1
- package/dist/chunk-ZOC7JSYC.js +0 -83
- package/dist/{chunk-XDDW4FRS.js → chunk-OIRNH6UO.js} +1 -1
|
@@ -1,29 +1,104 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import {
|
|
4
|
-
import { LaunchFilter } from './chunk-BCJNHMZT.js';
|
|
3
|
+
import { LaunchFilter } from './chunk-HHJDN6AU.js';
|
|
5
4
|
import { RepoFilter } from './chunk-TBT347UY.js';
|
|
6
5
|
import { hookExecSpoolPath } from './chunk-F7CU5ABH.js';
|
|
7
6
|
import { applyReportedCost } from './chunk-BKO6JEZI.js';
|
|
8
|
-
import { migrateLegacyArtifactSidecars, planArtifactUpload, artifactsStatePath, sanitizeTerminal, artifactBlobsDir, resolveArtifactTurnIndex, isArtifactTombstoned, ARTIFACT_RETRY_MAX_AGE_MS, writeArtifactSpoolState, reclaimUnneededArtifactBlobs, neededArtifactShas } from './chunk-
|
|
9
|
-
import { tailTranscript, enrichSessionRepo, CAPTURE_TIERS, scanAll, setRepoRoot, resolveRepoIdentity, inferArtifactKind, bannedPathsForTier, sessionIdFromPath, SOURCE_ADAPTERS, EVENT_TYPES } from './chunk-
|
|
7
|
+
import { migrateLegacyArtifactSidecars, planArtifactUpload, artifactsStatePath, sanitizeTerminal, artifactBlobsDir, resolveArtifactTurnIndex, isArtifactTombstoned, ARTIFACT_RETRY_MAX_AGE_MS, writeArtifactSpoolState, reclaimUnneededArtifactBlobs, neededArtifactShas } from './chunk-3DEVYTNQ.js';
|
|
8
|
+
import { setCustomScrubbers, setLocalIdentity, tailTranscript, enrichSessionRepo, CAPTURE_TIERS, scanAll, setRepoRoot, resolveRepoIdentity, inferArtifactKind, bannedPathsForTier, sessionIdFromPath, scrubDeep, downconvertSession, isHumanUserTurn, turnCountBound, SOURCE_ADAPTERS, EVENT_TYPES } from './chunk-ANK2B36Y.js';
|
|
10
9
|
import { C } from './chunk-YRYMNBGQ.js';
|
|
11
|
-
import { readLaunchSpoolState, launchStatusIsDecisive, launchedItemsBySession, launchesPath } from './chunk-
|
|
10
|
+
import { readLaunchSpoolState, launchStatusIsDecisive, launchedItemsBySession, launchesPath } from './chunk-GQD22KKN.js';
|
|
12
11
|
import { readLatestProvenanceForSession, readContextProvenance } from './chunk-ZJSNLVCE.js';
|
|
13
12
|
import { defaultFetch } from './chunk-BCLJSQCV.js';
|
|
14
13
|
import { resolveGatewayCredentials, unauthorizedMessage } from './chunk-BOLTI6LR.js';
|
|
15
14
|
import { readCloudConfig, cloudConfigPath, writeCloudConfig } from './chunk-EABW6AJQ.js';
|
|
16
15
|
import { signerFor, signRunnerRequest } from './chunk-M5POMKKW.js';
|
|
17
|
-
import { writeSyncStatus, spoolDir } from './chunk-
|
|
16
|
+
import { writeSyncStatus, spoolDir } from './chunk-FA2BOX4O.js';
|
|
18
17
|
import { removeJournalHooks } from './chunk-XCCLFVXM.js';
|
|
19
18
|
import { readRepoConnection, findGitDirs } from './chunk-BJ3KTMDH.js';
|
|
20
19
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
21
20
|
import { dirname, basename, join } from 'path';
|
|
22
21
|
import { statSync, readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync, existsSync, rmSync } from 'fs';
|
|
22
|
+
import { execFileSync } from 'child_process';
|
|
23
|
+
import { hostname, userInfo } from 'os';
|
|
23
24
|
import { fileURLToPath } from 'url';
|
|
24
25
|
import { createRequire as createRequire$1 } from 'module';
|
|
25
26
|
|
|
26
27
|
createRequire(import.meta.url);
|
|
28
|
+
function detectCi(env = process.env) {
|
|
29
|
+
return env.CI === "true" || env.CI === "1" || !!env.JENKINS_URL;
|
|
30
|
+
}
|
|
31
|
+
function gitGlobalConfig(key) {
|
|
32
|
+
try {
|
|
33
|
+
return execFileSync("git", ["config", "--global", key], { encoding: "utf8", timeout: 2e3 }).trim() || void 0;
|
|
34
|
+
} catch {
|
|
35
|
+
return void 0;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function installScrubContext(home, scrub) {
|
|
39
|
+
setCustomScrubbers(scrub);
|
|
40
|
+
setLocalIdentity({
|
|
41
|
+
name: gitGlobalConfig("user.name"),
|
|
42
|
+
email: gitGlobalConfig("user.email"),
|
|
43
|
+
username: (() => {
|
|
44
|
+
try {
|
|
45
|
+
return userInfo().username;
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
49
|
+
})(),
|
|
50
|
+
hostname: (() => {
|
|
51
|
+
try {
|
|
52
|
+
return hostname();
|
|
53
|
+
} catch {
|
|
54
|
+
return void 0;
|
|
55
|
+
}
|
|
56
|
+
})(),
|
|
57
|
+
home
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function applyContextProvenance(session, record) {
|
|
61
|
+
if (!record) return;
|
|
62
|
+
if (record.repository !== void 0) session.env.contextSourceRepo = record.repository;
|
|
63
|
+
if (record.ref !== void 0) session.env.contextSourceRef = record.ref;
|
|
64
|
+
if (record.sha !== null) session.env.contextSourceSha = record.sha;
|
|
65
|
+
session.env.contextSourceVerification = record.verification;
|
|
66
|
+
}
|
|
67
|
+
function prepareSessionForUpload(session, tier, envVars = process.env) {
|
|
68
|
+
setRepoRoot(resolveRepoIdentity(session.env.cwd).repoRoot);
|
|
69
|
+
scrubDeep(session);
|
|
70
|
+
session.workerKind ??= detectCi(envVars) ? "ci" : "seat";
|
|
71
|
+
return downconvertSession(session, tier);
|
|
72
|
+
}
|
|
73
|
+
var MAX_TOOL_IO_CHARS = 64e3;
|
|
74
|
+
function sliceToolIo(value) {
|
|
75
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
76
|
+
if (typeof text !== "string" || text.length <= MAX_TOOL_IO_CHARS) return value;
|
|
77
|
+
return `${text.slice(0, MAX_TOOL_IO_CHARS)}\u2026[sliced: ${text.length} chars exceeded the sync per-call cap]`;
|
|
78
|
+
}
|
|
79
|
+
function sliceTurnIo(turn) {
|
|
80
|
+
return {
|
|
81
|
+
...turn,
|
|
82
|
+
toolCalls: (turn.toolCalls ?? []).map((call) => ({
|
|
83
|
+
...call,
|
|
84
|
+
...call.input !== void 0 ? { input: sliceToolIo(call.input) } : {},
|
|
85
|
+
...call.output !== void 0 ? { output: sliceToolIo(call.output) } : {}
|
|
86
|
+
}))
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function wholeSessionCounts(turns) {
|
|
90
|
+
const allCalls = turns.flatMap((t) => t.toolCalls ?? []);
|
|
91
|
+
return {
|
|
92
|
+
// An index BOUND (`1 + max(turn.index)`), not the shipped turn count —
|
|
93
|
+
// turn indexes are not guaranteed dense from zero, and a late artifact
|
|
94
|
+
// anchored at its real turn index must still validate against the
|
|
95
|
+
// summary row this produces (see turnCountBound).
|
|
96
|
+
turns: turnCountBound(turns),
|
|
97
|
+
toolCalls: allCalls.length,
|
|
98
|
+
errors: allCalls.filter((c) => c.status === "error").length,
|
|
99
|
+
userTurns: turns.filter(isHumanUserTurn).length
|
|
100
|
+
};
|
|
101
|
+
}
|
|
27
102
|
var IRRELEVANT = /* @__PURE__ */ new Set(["__tests__", "__mocks__", "node_modules"]);
|
|
28
103
|
function newestSourceMs(dir) {
|
|
29
104
|
let newest = 0;
|
|
@@ -481,6 +556,8 @@ function retryAfterMs(response) {
|
|
|
481
556
|
}
|
|
482
557
|
var LiveUploader = class {
|
|
483
558
|
constructor(opts) {
|
|
559
|
+
/** Set by `stop()`: no more debounce or retry waits, so what is pending is sent once and given up on. */
|
|
560
|
+
this.stopping = false;
|
|
484
561
|
this.cycles = /* @__PURE__ */ new Map();
|
|
485
562
|
/** Sessions this sender is done with: ended, or permanently rejected. The
|
|
486
563
|
* daemon keeps mirroring a transcript after its session closes, and every
|
|
@@ -491,12 +568,31 @@ var LiveUploader = class {
|
|
|
491
568
|
* every session, so the sender stops entirely for the rest of this
|
|
492
569
|
* process rather than retrying session by session. */
|
|
493
570
|
this.stopped = false;
|
|
571
|
+
this.warned = /* @__PURE__ */ new Set();
|
|
494
572
|
this.home = opts.home;
|
|
495
573
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
496
574
|
this.sleepImpl = opts.sleepImpl ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
497
575
|
this.debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
498
576
|
this.env = opts.env ?? process.env;
|
|
499
577
|
this.stderr = opts.stderr ?? ((s) => void process.stderr.write(s));
|
|
578
|
+
this.build = opts.build;
|
|
579
|
+
this.onUnauthorized = opts.onUnauthorized;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Final pass for a build sender: sends what is pending now, without waiting out the debounce and without
|
|
583
|
+
* retrying a failed send, and resolves once every cycle has finished or `timeoutMs` has passed.
|
|
584
|
+
*/
|
|
585
|
+
async stop(timeoutMs) {
|
|
586
|
+
this.stopping = true;
|
|
587
|
+
let timer;
|
|
588
|
+
const expired = new Promise((resolve) => {
|
|
589
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
await Promise.race([this.flush(), expired]);
|
|
593
|
+
} finally {
|
|
594
|
+
clearTimeout(timer);
|
|
595
|
+
}
|
|
500
596
|
}
|
|
501
597
|
/** Called for every daemon `mirrored` event. Cheap and synchronous — the
|
|
502
598
|
* actual read/send work happens on the debounced cycle. */
|
|
@@ -536,7 +632,7 @@ var LiveUploader = class {
|
|
|
536
632
|
for (; ; ) {
|
|
537
633
|
for (; ; ) {
|
|
538
634
|
const generationAtWaitStart = cycle.generation;
|
|
539
|
-
await this.sleepImpl(this.debounceMs);
|
|
635
|
+
if (!this.stopping) await this.sleepImpl(this.debounceMs);
|
|
540
636
|
if (cycle.generation !== generationAtWaitStart) continue;
|
|
541
637
|
break;
|
|
542
638
|
}
|
|
@@ -562,8 +658,21 @@ var LiveUploader = class {
|
|
|
562
658
|
const priorEntry = state.sessions[key];
|
|
563
659
|
if (priorEntry?.ended || priorEntry?.failed) return true;
|
|
564
660
|
const cloudConfig = readCloudConfig(this.home);
|
|
565
|
-
|
|
566
|
-
if (
|
|
661
|
+
let destination;
|
|
662
|
+
if (this.build === void 0) {
|
|
663
|
+
if (cloudConfig.autoSync === false) return false;
|
|
664
|
+
if (!cloudConfig.url || !(cloudConfig.apiKey || cloudConfig.accountToken)) return false;
|
|
665
|
+
} else {
|
|
666
|
+
const url = this.env.OUTERLAYER_URL;
|
|
667
|
+
const credential = this.env.OUTERLAYER_API_KEY;
|
|
668
|
+
const appId = this.env.OUTERLAYER_APP_ID;
|
|
669
|
+
if (!url || !credential || !appId) {
|
|
670
|
+
this.warnOnce("incomplete", `live upload is idle: this build's environment lacks ${[!url && "OUTERLAYER_URL", !credential && "OUTERLAYER_API_KEY", !appId && "OUTERLAYER_APP_ID"].filter(Boolean).join(", ")}, so nothing streams until \`outerlayer sync\` runs.
|
|
671
|
+
`);
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
destination = { ok: true, url, credential, appId, kind: "key" };
|
|
675
|
+
}
|
|
567
676
|
const info = subagentInfo(mirrorPath);
|
|
568
677
|
const tail = tailTranscript(mirrorPath, void 0, {
|
|
569
678
|
isSubagent: info.isSubagent,
|
|
@@ -573,17 +682,16 @@ var LiveUploader = class {
|
|
|
573
682
|
if (!tail.session) return false;
|
|
574
683
|
const session = tail.session;
|
|
575
684
|
enrichSessionRepo(session);
|
|
576
|
-
|
|
685
|
+
destination ??= resolveGatewayCredentials({ home: this.home, env: {}, cwd: session.env.cwd ?? "/" });
|
|
577
686
|
if (!destination.ok) return false;
|
|
578
|
-
|
|
579
|
-
if (!repoFilter.allows(session.env.gitRepo)) return false;
|
|
687
|
+
if (this.build === void 0 && !new RepoFilter(cloudConfig.repos).allows(session.env.gitRepo)) return false;
|
|
580
688
|
const launchSpool = readLaunchSpoolState(this.home);
|
|
581
689
|
if (!launchStatusIsDecisive(launchSpool)) return false;
|
|
582
|
-
const launchFilter = new LaunchFilter(launchSpool.ids);
|
|
690
|
+
const launchFilter = new LaunchFilter(launchSpool.ids, launchSpool.records, this.build);
|
|
583
691
|
if (!launchFilter.allows(session)) return false;
|
|
584
692
|
let tier;
|
|
585
693
|
try {
|
|
586
|
-
tier = resolveTier(cloudConfig.tier);
|
|
694
|
+
tier = resolveTier(this.build === void 0 ? cloudConfig.tier : this.env.OUTERLAYER_TIER ?? cloudConfig.tier);
|
|
587
695
|
} catch (err) {
|
|
588
696
|
if (err instanceof SyncConfigError) return false;
|
|
589
697
|
throw err;
|
|
@@ -613,6 +721,7 @@ var LiveUploader = class {
|
|
|
613
721
|
const outcome = await this.sendWithRetry(endpoint, destination.credential, destination.appId, prepared.id, payload);
|
|
614
722
|
if (outcome === "unauthorized") {
|
|
615
723
|
this.stopped = true;
|
|
724
|
+
this.onUnauthorized?.();
|
|
616
725
|
this.stderr(
|
|
617
726
|
destination.credential.startsWith("olu_") ? `live upload stopped: the login was refused (expired or revoked). No session streams from this daemon until you run \`outerlayer login\` and it restarts.
|
|
618
727
|
` : `live upload stopped: the API key was refused. No session streams from this daemon until it restarts with a valid key.
|
|
@@ -620,8 +729,9 @@ var LiveUploader = class {
|
|
|
620
729
|
);
|
|
621
730
|
return true;
|
|
622
731
|
}
|
|
732
|
+
if (outcome === "unreachable") return false;
|
|
623
733
|
if (outcome === "rejected") {
|
|
624
|
-
const reason =
|
|
734
|
+
const reason = `the server refused this delta permanently${this.lastRejectStatus === void 0 ? "" : ` (HTTP ${this.lastRejectStatus})`}`;
|
|
625
735
|
this.mergeSessionState(key, {
|
|
626
736
|
ackedTurnIndex: currentAcked,
|
|
627
737
|
failed: true,
|
|
@@ -654,6 +764,7 @@ var LiveUploader = class {
|
|
|
654
764
|
* key bound to a host key is refused on every route unless that key signed the request. A login token is
|
|
655
765
|
* never signed: only the saved factory key is bound to a host key. */
|
|
656
766
|
async signatureHeaders(endpoint, body, credential) {
|
|
767
|
+
if (this.build !== void 0) return {};
|
|
657
768
|
if (readCloudConfig(this.home).apiKey !== credential) return {};
|
|
658
769
|
let signer;
|
|
659
770
|
try {
|
|
@@ -664,6 +775,12 @@ var LiveUploader = class {
|
|
|
664
775
|
if (signer === void 0) return {};
|
|
665
776
|
return { ...await signRunnerRequest({ method: "POST", url: endpoint, body: new TextEncoder().encode(body), signer }) };
|
|
666
777
|
}
|
|
778
|
+
/** Writes `message` the first time `key` is seen, so a condition that holds on every fire logs once. */
|
|
779
|
+
warnOnce(key, message) {
|
|
780
|
+
if (this.warned.has(key)) return;
|
|
781
|
+
this.warned.add(key);
|
|
782
|
+
this.stderr(message);
|
|
783
|
+
}
|
|
667
784
|
/** Sends one request, retrying a network error or a retryable 5xx forever
|
|
668
785
|
* with exponential backoff (2s → 60s cap) — the daemon runs unattended, so
|
|
669
786
|
* there is no bounded attempt count. A 401 or a permanent 4xx/rejection
|
|
@@ -673,6 +790,7 @@ var LiveUploader = class {
|
|
|
673
790
|
let delayMs = RETRY_BASE_DELAY_MS;
|
|
674
791
|
for (; ; ) {
|
|
675
792
|
let response;
|
|
793
|
+
let failure = "";
|
|
676
794
|
try {
|
|
677
795
|
const signature = await this.signatureHeaders(endpoint, body, apiKey);
|
|
678
796
|
response = await this.fetchImpl(endpoint, {
|
|
@@ -685,18 +803,30 @@ var LiveUploader = class {
|
|
|
685
803
|
},
|
|
686
804
|
body
|
|
687
805
|
});
|
|
688
|
-
} catch {
|
|
806
|
+
} catch (err) {
|
|
689
807
|
response = void 0;
|
|
808
|
+
failure = err instanceof Error ? err.message : String(err);
|
|
690
809
|
}
|
|
691
810
|
if (response) {
|
|
692
811
|
if (response.status === 401) return "unauthorized";
|
|
693
812
|
if (response.ok) {
|
|
694
813
|
const accepted = await acceptedSessionIds(response);
|
|
695
|
-
if (accepted)
|
|
814
|
+
if (accepted) {
|
|
815
|
+
if (accepted.includes(sessionId)) return "accepted";
|
|
816
|
+
this.lastRejectStatus = void 0;
|
|
817
|
+
return "rejected";
|
|
818
|
+
}
|
|
696
819
|
} else if (!RETRYABLE_STATUSES.has(response.status)) {
|
|
820
|
+
this.lastRejectStatus = response.status;
|
|
697
821
|
return "rejected";
|
|
698
822
|
}
|
|
699
823
|
}
|
|
824
|
+
if (this.build !== void 0) {
|
|
825
|
+
const why = response === void 0 ? failure : `the gateway answered ${response.status}`;
|
|
826
|
+
this.stderr(`live upload of session ${sessionId} failed: ${why}${this.stopping ? "" : " \xB7 retrying"}
|
|
827
|
+
`);
|
|
828
|
+
}
|
|
829
|
+
if (this.stopping) return "unreachable";
|
|
700
830
|
await this.sleepImpl(Math.max(retryAfterMs(response), delayMs));
|
|
701
831
|
delayMs = Math.min(delayMs * 2, RETRY_MAX_DELAY_MS);
|
|
702
832
|
}
|
|
@@ -1683,4 +1813,4 @@ ${C.yellow}!${C.reset} batch ${shippedBatches + 1}/${batches.length} accounting
|
|
|
1683
1813
|
}
|
|
1684
1814
|
}
|
|
1685
1815
|
|
|
1686
|
-
export { CHUNK_PART_TARGET_BYTES, LiveUploader, RETRYABLE_STATUSES, SyncConfigError, SyncTransportError, isLoopbackUrl, resolveTier, runSync, staleBuildRefusal, staleBuildWarning };
|
|
1816
|
+
export { CHUNK_PART_TARGET_BYTES, LiveUploader, RETRYABLE_STATUSES, SyncConfigError, SyncTransportError, detectCi, installScrubContext, isLoopbackUrl, resolveTier, runSync, staleBuildRefusal, staleBuildWarning };
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { writeJobFile, readJobFile } from './chunk-T6OIJQRT.js';
|
|
4
|
+
import { senderPidfile } from './chunk-ZILGTTY6.js';
|
|
3
5
|
import { DOCKERD_LOG } from './chunk-CBPPJSAR.js';
|
|
4
|
-
import { installScrubContext } from './chunk-
|
|
5
|
-
import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote,
|
|
6
|
+
import { installScrubContext } from './chunk-UMFWVGJO.js';
|
|
7
|
+
import { AssetError, describeCallFailure, obtainImage, ensureRootfs, gitRemoteFor, startServicesFor, createBuildDisk, machineConfig, refusalCode, resolveAgentUser, AgentUserError, runBuildSteps, removeStagedContext, stageGovernedContext, shellQuote, createRunnerLog, createContainerRuntime, stopContainer, syncInContainer, formatDuration, cleanupContainer, retrySend, formatClock, containerSenderCommand, GH_CONFIG_YML, ghHostsYml, ADDED_USER, isLiteralAddress, signalInContainer, deleteBuildDisk, senderSignalScript } from './chunk-RZSF5B46.js';
|
|
6
8
|
import { CLAUDE_CODE_VERSION } from './chunk-Z62HNKEJ.js';
|
|
7
9
|
import { isSameProcess, startTimeOf } from './chunk-N5FOE5PS.js';
|
|
8
10
|
import { childEnvironment } from './chunk-774HEQL6.js';
|
|
9
11
|
import { waitForAgent, vmExecArgs } from './chunk-W4SQCOZU.js';
|
|
10
12
|
import { socketPathProblem, vsockLinks, linkVsockSockets } from './chunk-B2G7JHHB.js';
|
|
11
|
-
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-
|
|
12
|
-
import { planArtifactUpload } from './chunk-
|
|
13
|
-
import { scrubText } from './chunk-
|
|
14
|
-
import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-
|
|
13
|
+
import { WorkCommandError, postJson, isSignatureRefusal, getJson } from './chunk-BNMCXPG7.js';
|
|
14
|
+
import { planArtifactUpload } from './chunk-3DEVYTNQ.js';
|
|
15
|
+
import { scrubText } from './chunk-ANK2B36Y.js';
|
|
16
|
+
import { isBuiltinContainer, isBuiltinProcess, isBuiltinVm } from './chunk-3DD6AOWC.js';
|
|
15
17
|
import { parseSize } from './chunk-F6GFZXZ3.js';
|
|
16
18
|
import { claudeCredential, buildEnvironment, JOB_MOUNT, CHECKOUT_DIR, parseIsolation, proxyEnvironment, containerLocalAddress, destinationEnvironment, CLAUDE_CREDENTIAL_VARIABLES, builtInEnvironment, withHooksPath, startBuildServices, BUILT_IN_NAMES, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, RELAY_PORT } from './chunk-AUFG23AA.js';
|
|
17
19
|
import { readCloudConfig } from './chunk-EABW6AJQ.js';
|
|
18
20
|
import { resolveVersion } from './chunk-RA3O54FC.js';
|
|
19
21
|
import { signerFor } from './chunk-M5POMKKW.js';
|
|
20
|
-
import { runnerDir, buildDir, vsockPath,
|
|
22
|
+
import { runnerDir, buildDir, vsockPath, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
|
|
21
23
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
22
24
|
import { spawn, spawnSync } from 'child_process';
|
|
23
25
|
import { randomBytes } from 'crypto';
|
|
24
|
-
import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, existsSync, readFileSync,
|
|
26
|
+
import { mkdirSync, chmodSync, writeFileSync, appendFileSync, statSync, accessSync, constants, openSync, existsSync, readFileSync, closeSync, rmSync, readdirSync } from 'fs';
|
|
25
27
|
import { join, isAbsolute, dirname, delimiter } from 'path';
|
|
26
28
|
|
|
27
29
|
createRequire(import.meta.url);
|
|
@@ -192,9 +194,15 @@ async function provisionMicroVm(input) {
|
|
|
192
194
|
return bail(`the build's disk and machine could not be made: ${describeCallFailure(err)}`);
|
|
193
195
|
}
|
|
194
196
|
if (input.aborted()) return bail("", true);
|
|
197
|
+
try {
|
|
198
|
+
state.cgroup = vm.cgroup.create(buildId, limits.memoryBytes);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return bail(`the build's memory limit could not be set: ${describeCallFailure(err)}`);
|
|
201
|
+
}
|
|
202
|
+
if (state.cgroup !== void 0) input.save(state, environment);
|
|
195
203
|
const consolePath = join(jobDir, "vm-console.log");
|
|
196
204
|
try {
|
|
197
|
-
state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath });
|
|
205
|
+
state.firecracker = await vm.firecracker.spawn({ binary: assets.firecracker, configPath, consolePath, cgroup: state.cgroup });
|
|
198
206
|
} catch (err) {
|
|
199
207
|
return bail(`Firecracker did not start: ${describeCallFailure(err)}`);
|
|
200
208
|
}
|
|
@@ -315,7 +323,7 @@ function agentVersionOf(toolsJson) {
|
|
|
315
323
|
}
|
|
316
324
|
return CLAUDE_CODE_VERSION;
|
|
317
325
|
}
|
|
318
|
-
function
|
|
326
|
+
function vmExecFor(input, argv) {
|
|
319
327
|
const state = input.job.microvm;
|
|
320
328
|
const env = buildEnvironment({
|
|
321
329
|
config: input.config,
|
|
@@ -334,13 +342,17 @@ function microVmCommandLine(input) {
|
|
|
334
342
|
cwd: CHECKOUT_DIR,
|
|
335
343
|
env: { ...state.imageEnv, ...env.literals },
|
|
336
344
|
pass: Object.keys(env.passed).filter((name) => env.passed[name] !== void 0),
|
|
337
|
-
argv
|
|
338
|
-
};
|
|
339
|
-
return {
|
|
340
|
-
line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "),
|
|
341
|
-
options,
|
|
342
|
-
env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
|
|
345
|
+
argv
|
|
343
346
|
};
|
|
347
|
+
return { options, env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
|
|
348
|
+
}
|
|
349
|
+
function microVmCommandLine(input) {
|
|
350
|
+
const { options, env } = vmExecFor(input, ["/bin/sh", "-c", input.cmdline]);
|
|
351
|
+
return { line: [process.execPath, input.cli, "vm-exec", ...vmExecArgs(options)].map(shellQuote).join(" "), options, env };
|
|
352
|
+
}
|
|
353
|
+
function microVmSenderCommand(input) {
|
|
354
|
+
const { options, env } = vmExecFor(input, ["outerlayer", "session-sender"]);
|
|
355
|
+
return { command: process.execPath, args: [input.cli, "vm-exec", ...vmExecArgs(options)], env };
|
|
344
356
|
}
|
|
345
357
|
function isRunning(runtime, state) {
|
|
346
358
|
return state.firecracker !== void 0 && runtime.vm.firecracker.alive(state.firecracker);
|
|
@@ -391,6 +403,13 @@ async function cleanupMicroVm(runtime, state, jobDir) {
|
|
|
391
403
|
failures.push(`the microVM (pid ${state.firecracker.pid}) was not stopped: ${describeCallFailure(err)}`);
|
|
392
404
|
}
|
|
393
405
|
}
|
|
406
|
+
if (state.cgroup !== void 0) {
|
|
407
|
+
try {
|
|
408
|
+
runtime.vm.cgroup.remove(state.cgroup);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
failures.push(`the build's cgroup ${state.cgroup} was not removed: ${describeCallFailure(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
394
413
|
if (state.buildDisk !== void 0) deleteBuildDisk(state.buildDisk);
|
|
395
414
|
const tunnel = runtime.tunnels.get(state.buildId);
|
|
396
415
|
if (tunnel !== void 0) {
|
|
@@ -410,6 +429,8 @@ async function stopOrphanedMicroVms(input) {
|
|
|
410
429
|
process.kill(-job.pid, "SIGKILL");
|
|
411
430
|
} catch {
|
|
412
431
|
}
|
|
432
|
+
const deadline = Date.now() + 2e3;
|
|
433
|
+
while (isSameProcess(job.pid, job.start) && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 10));
|
|
413
434
|
}
|
|
414
435
|
if (state.firecracker !== void 0 && input.runtime.vm.firecracker.alive(state.firecracker)) {
|
|
415
436
|
await input.runtime.vm.firecracker.stop(state.firecracker);
|
|
@@ -581,60 +602,6 @@ async function closeHostBuild(runtime, build) {
|
|
|
581
602
|
rmSync(build.buildDir, { recursive: true, force: true });
|
|
582
603
|
if (build.workDir !== void 0) rmSync(build.workDir, { recursive: true, force: true });
|
|
583
604
|
}
|
|
584
|
-
function readJobFile(path) {
|
|
585
|
-
if (!existsSync(path)) return null;
|
|
586
|
-
try {
|
|
587
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
588
|
-
} catch {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
function writeJobFile(path, job) {
|
|
593
|
-
mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
594
|
-
writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
|
|
595
|
-
}
|
|
596
|
-
var ENDED = "ended before provision finished";
|
|
597
|
-
async function provisionProcess(input) {
|
|
598
|
-
const { config, runtime, item, jobDir } = input;
|
|
599
|
-
if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
|
|
600
|
-
const repository = item.specRepository;
|
|
601
|
-
if (repository === void 0 || repository === null || repository === "") {
|
|
602
|
-
return { ok: false, reason: "the work item names no repository to clone" };
|
|
603
|
-
}
|
|
604
|
-
const buildId = randomBytes(6).toString("hex");
|
|
605
|
-
const buildDir2 = buildDir(config.configPath, buildId);
|
|
606
|
-
const workDir = join(jobDir, "work");
|
|
607
|
-
const state = { buildId, buildDir: buildDir2, workDir };
|
|
608
|
-
const environment = sharedUserEnvironment(input.cliVersion);
|
|
609
|
-
input.save(state, environment);
|
|
610
|
-
const bail = async (reason) => {
|
|
611
|
-
try {
|
|
612
|
-
await cleanupProcess(runtime, state);
|
|
613
|
-
} catch (err) {
|
|
614
|
-
input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
|
|
615
|
-
}
|
|
616
|
-
return { ok: false, reason };
|
|
617
|
-
};
|
|
618
|
-
if (input.aborted()) return bail(ENDED);
|
|
619
|
-
mkdirSync(buildDir2, { recursive: true });
|
|
620
|
-
chmodSync(buildDir2, 493);
|
|
621
|
-
try {
|
|
622
|
-
await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
|
|
623
|
-
} catch (err) {
|
|
624
|
-
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
625
|
-
}
|
|
626
|
-
input.save(state, environment);
|
|
627
|
-
if (input.aborted()) return bail(ENDED);
|
|
628
|
-
const checkout = join(workDir, "checkout");
|
|
629
|
-
const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
|
|
630
|
-
const cloned = await runtime.clone({ remote, dest: checkout });
|
|
631
|
-
if (!cloned.ok) return bail(cloned.reason);
|
|
632
|
-
if (input.aborted()) return bail(ENDED);
|
|
633
|
-
return { ok: true, workdir: checkout, state, environment };
|
|
634
|
-
}
|
|
635
|
-
function cleanupProcess(runtime, state) {
|
|
636
|
-
return closeHostBuild(runtime, state);
|
|
637
|
-
}
|
|
638
605
|
|
|
639
606
|
// src/runner/verdict.ts
|
|
640
607
|
var VERDICT_SETTLE_MS = 5 * 6e4;
|
|
@@ -708,6 +675,48 @@ async function settleVerdict(deps) {
|
|
|
708
675
|
await deps.sleep(everyMs);
|
|
709
676
|
}
|
|
710
677
|
}
|
|
678
|
+
var ENDED = "ended before provision finished";
|
|
679
|
+
async function provisionProcess(input) {
|
|
680
|
+
const { config, runtime, item, jobDir } = input;
|
|
681
|
+
if (claudeCredential(runtime.hostEnv) === void 0) return { ok: false, reason: AGENT_CREDENTIAL_MISSING };
|
|
682
|
+
const repository = item.specRepository;
|
|
683
|
+
if (repository === void 0 || repository === null || repository === "") {
|
|
684
|
+
return { ok: false, reason: "the work item names no repository to clone" };
|
|
685
|
+
}
|
|
686
|
+
const buildId = randomBytes(6).toString("hex");
|
|
687
|
+
const buildDir2 = buildDir(config.configPath, buildId);
|
|
688
|
+
const workDir = join(jobDir, "work");
|
|
689
|
+
const state = { buildId, buildDir: buildDir2, workDir };
|
|
690
|
+
const environment = sharedUserEnvironment(input.cliVersion);
|
|
691
|
+
input.save(state, environment);
|
|
692
|
+
const bail = async (reason) => {
|
|
693
|
+
try {
|
|
694
|
+
await cleanupProcess(runtime, state);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
input.log.error(`#${item.number} cleanup failed: ${describeCallFailure(err)}`);
|
|
697
|
+
}
|
|
698
|
+
return { ok: false, reason };
|
|
699
|
+
};
|
|
700
|
+
if (input.aborted()) return bail(ENDED);
|
|
701
|
+
mkdirSync(buildDir2, { recursive: true });
|
|
702
|
+
chmodSync(buildDir2, 493);
|
|
703
|
+
try {
|
|
704
|
+
await startHostServices({ runtime, config, build: state, jobDir, itemKey: input.itemKey, process: true });
|
|
705
|
+
} catch (err) {
|
|
706
|
+
return bail(`the tunnel could not start: ${describeCallFailure(err)}`);
|
|
707
|
+
}
|
|
708
|
+
input.save(state, environment);
|
|
709
|
+
if (input.aborted()) return bail(ENDED);
|
|
710
|
+
const checkout = join(workDir, "checkout");
|
|
711
|
+
const remote = runtime.remoteFor?.(repository) ?? `https://github.com/${repository}.git`;
|
|
712
|
+
const cloned = await runtime.clone({ remote, dest: checkout });
|
|
713
|
+
if (!cloned.ok) return bail(cloned.reason);
|
|
714
|
+
if (input.aborted()) return bail(ENDED);
|
|
715
|
+
return { ok: true, workdir: checkout, state, environment };
|
|
716
|
+
}
|
|
717
|
+
function cleanupProcess(runtime, state) {
|
|
718
|
+
return closeHostBuild(runtime, state);
|
|
719
|
+
}
|
|
711
720
|
|
|
712
721
|
// src/runner/job.ts
|
|
713
722
|
var LEASE_SECONDS = 900;
|
|
@@ -1155,6 +1164,11 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1155
1164
|
const jobFilePath = join(jobDirPath, "job.json");
|
|
1156
1165
|
const logPath = join(jobDirPath, "command.log");
|
|
1157
1166
|
const tag = `#${job.item}`;
|
|
1167
|
+
if (job.sender !== void 0) {
|
|
1168
|
+
await stopSender({ job, deps: { spawnFn, runtime: deps.container, log } });
|
|
1169
|
+
delete job.sender;
|
|
1170
|
+
writeJobFile(jobFilePath, job);
|
|
1171
|
+
}
|
|
1158
1172
|
const build = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.container ?? createContainerRuntime() };
|
|
1159
1173
|
const vmBuild = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.container ?? createContainerRuntime() };
|
|
1160
1174
|
if (build !== void 0 && job.pid !== void 0 && isGroupAlive(job.pid)) {
|
|
@@ -1177,6 +1191,20 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1177
1191
|
job.reason = scrubReason(ending.reason, config);
|
|
1178
1192
|
writeJobFile(jobFilePath, job);
|
|
1179
1193
|
}
|
|
1194
|
+
let outOfMemory;
|
|
1195
|
+
if (vmBuild?.state.cgroup !== void 0 && outcome !== void 0 && outcome !== "ok" && ending?.reason === void 0) {
|
|
1196
|
+
try {
|
|
1197
|
+
if (vmBuild.runtime.vm.cgroup.oomKills(vmBuild.state.cgroup) > 0) {
|
|
1198
|
+
outOfMemory = `the build ran out of memory: the kernel killed its microVM at the limit of runner.build.memory (${config.build?.memory ?? "8g"}) plus 256 MiB for Firecracker. Raise runner.build.memory, or lower runner.concurrency`;
|
|
1199
|
+
}
|
|
1200
|
+
} catch (err) {
|
|
1201
|
+
log.error(`${tag} could not read the build's memory events: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1202
|
+
}
|
|
1203
|
+
if (outOfMemory !== void 0) {
|
|
1204
|
+
job.reason = scrubReason(outOfMemory, config);
|
|
1205
|
+
writeJobFile(jobFilePath, job);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1180
1208
|
const hookLimits = () => hookLimitsFor(config, now().getTime(), deps.now);
|
|
1181
1209
|
let tunnelRecord;
|
|
1182
1210
|
if (!job.cleanupRan) {
|
|
@@ -1340,6 +1368,8 @@ async function finishJob(job, jobDirPath, outcome, config, deps, ending) {
|
|
|
1340
1368
|
const fromHook = readReasonLine(jobDirPath, config);
|
|
1341
1369
|
if (verdictReason !== void 0) {
|
|
1342
1370
|
job.reason = verdictReason;
|
|
1371
|
+
} else if (outOfMemory !== void 0) {
|
|
1372
|
+
job.reason = scrubReason(outOfMemory, config);
|
|
1343
1373
|
} else if (ending?.reason === void 0) {
|
|
1344
1374
|
job.reason = (outcome === "ok" ? void 0 : held?.reason) ?? (isolated ? fromHook ?? job.reason : fromHook);
|
|
1345
1375
|
}
|
|
@@ -1458,4 +1488,109 @@ async function recoverLostJobs(jobsDirPath, config, deps) {
|
|
|
1458
1488
|
return recovered;
|
|
1459
1489
|
}
|
|
1460
1490
|
|
|
1461
|
-
|
|
1491
|
+
// src/runner/sender.ts
|
|
1492
|
+
var STOP_WAIT_MS = 3e4;
|
|
1493
|
+
var POLL_MS = 200;
|
|
1494
|
+
var BUILD_HOME = "/build/home";
|
|
1495
|
+
async function startSender(input) {
|
|
1496
|
+
const { config, job, jobDir, deps } = input;
|
|
1497
|
+
const tag = `#${job.item}`;
|
|
1498
|
+
if (job.itemKey === void 0) return void 0;
|
|
1499
|
+
const spawnFn = deps.spawnFn ?? spawn;
|
|
1500
|
+
try {
|
|
1501
|
+
const launch = launchFor(config, job, jobDir, deps);
|
|
1502
|
+
const fd = openSync(join(jobDir, "sender.log"), "a", RUNNER_FILE_MODE);
|
|
1503
|
+
let pid;
|
|
1504
|
+
try {
|
|
1505
|
+
const child = spawnFn(launch.command, launch.args, { env: launch.env, cwd: launch.cwd, detached: true, stdio: ["ignore", fd, fd] });
|
|
1506
|
+
child.on("error", (err) => deps.log.error(`${tag} session sender failed: ${describeCallFailure(err)}`));
|
|
1507
|
+
child.on("exit", (code, signal) => deps.log.line(`${tag} session sender exited \xB7 ${signal ?? `code ${code}`} \xB7 see sender.log`));
|
|
1508
|
+
child.unref?.();
|
|
1509
|
+
pid = child.pid;
|
|
1510
|
+
} finally {
|
|
1511
|
+
closeSync(fd);
|
|
1512
|
+
}
|
|
1513
|
+
if (pid === void 0) {
|
|
1514
|
+
deps.log.line(`${tag} session sender did not start: no pid`);
|
|
1515
|
+
return void 0;
|
|
1516
|
+
}
|
|
1517
|
+
deps.log.line(`${tag} session sender started \xB7 pid ${pid}`);
|
|
1518
|
+
return { pid, start: startTimeOf(pid) ?? void 0 };
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
deps.log.error(`${tag} session sender did not start: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1521
|
+
return void 0;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
function launchFor(config, job, jobDir, deps) {
|
|
1525
|
+
if (job.microvm !== void 0) {
|
|
1526
|
+
const runtime = deps.runtime ?? createContainerRuntime();
|
|
1527
|
+
const cli = deps.cliPath ?? join(runtime.cliBuild().distDir, "index.js");
|
|
1528
|
+
return { ...microVmSenderCommand({ job, runtime, config, cli }), cwd: jobDir };
|
|
1529
|
+
}
|
|
1530
|
+
if (job.container !== void 0) {
|
|
1531
|
+
return { ...containerSenderCommand({ job, runtime: deps.runtime ?? createContainerRuntime(), config }), cwd: jobDir };
|
|
1532
|
+
}
|
|
1533
|
+
const env = jobEnv(config, job.item, job.itemKey, jobDir, process.env, job.branch, job.commitIdentity?.author);
|
|
1534
|
+
const build = job.build;
|
|
1535
|
+
const hostEnv = build === void 0 ? env : hostCommandEnvironment(env, build, config, job.commitIdentity?.hooksPath);
|
|
1536
|
+
if (build?.exec !== void 0) {
|
|
1537
|
+
return { command: build.exec, args: execArgv(job.workdir, ["outerlayer", "session-sender"]), env: hostEnv, cwd: jobDir };
|
|
1538
|
+
}
|
|
1539
|
+
return { command: process.execPath, args: [deps.cliPath ?? process.argv[1] ?? "outerlayer", "session-sender"], env: hostEnv, cwd: job.workdir ?? jobDir };
|
|
1540
|
+
}
|
|
1541
|
+
function isLive(sender) {
|
|
1542
|
+
if (sender.start !== void 0) return isSameProcess(sender.pid, sender.start);
|
|
1543
|
+
try {
|
|
1544
|
+
process.kill(sender.pid, 0);
|
|
1545
|
+
return true;
|
|
1546
|
+
} catch {
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
function signalHost(sender, signal) {
|
|
1551
|
+
if (!isLive(sender)) return;
|
|
1552
|
+
try {
|
|
1553
|
+
process.kill(-sender.pid, signal);
|
|
1554
|
+
} catch {
|
|
1555
|
+
try {
|
|
1556
|
+
process.kill(sender.pid, signal);
|
|
1557
|
+
} catch {
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
async function signalInGuest(runtime, state, pidfile, signal) {
|
|
1562
|
+
await runtime.vm.exec(state.vsockPath, {
|
|
1563
|
+
argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)],
|
|
1564
|
+
user: state.agentUser ?? ADDED_USER,
|
|
1565
|
+
cwd: "/",
|
|
1566
|
+
env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
async function stopSender(input) {
|
|
1570
|
+
const { job, deps } = input;
|
|
1571
|
+
const sender = job.sender;
|
|
1572
|
+
if (sender === void 0) return;
|
|
1573
|
+
const tag = `#${job.item}`;
|
|
1574
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1575
|
+
const inContainer = job.container === void 0 ? void 0 : { state: job.container, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
1576
|
+
const inGuest = job.microvm === void 0 ? void 0 : { state: job.microvm, runtime: deps.runtime ?? createContainerRuntime(), pidfile: senderPidfile(BUILD_HOME, Number(job.item)) };
|
|
1577
|
+
const signalInside = async (signal) => {
|
|
1578
|
+
if (inContainer !== void 0) await signalInContainer(inContainer.runtime, inContainer.state, inContainer.pidfile, signal).catch(() => void 0);
|
|
1579
|
+
if (inGuest !== void 0) await signalInGuest(inGuest.runtime, inGuest.state, inGuest.pidfile, signal).catch(() => void 0);
|
|
1580
|
+
};
|
|
1581
|
+
try {
|
|
1582
|
+
if (inContainer !== void 0 || inGuest !== void 0) await signalInside("TERM");
|
|
1583
|
+
else signalHost(sender, "SIGTERM");
|
|
1584
|
+
const deadline = (deps.waitMs ?? STOP_WAIT_MS) / POLL_MS;
|
|
1585
|
+
for (let waited = 0; isLive(sender) && waited < deadline; waited += 1) await sleep(POLL_MS);
|
|
1586
|
+
if (isLive(sender)) {
|
|
1587
|
+
deps.log.error(`${tag} session sender did not stop in time \xB7 killing it`);
|
|
1588
|
+
await signalInside("KILL");
|
|
1589
|
+
signalHost(sender, "SIGKILL");
|
|
1590
|
+
}
|
|
1591
|
+
} catch (err) {
|
|
1592
|
+
deps.log.error(`${tag} stopping the session sender failed: ${describeCallFailure(err)} \xB7 continuing`);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
export { ALREADY_RELEASED, LEASE_SECONDS, RENEW_INTERVAL_SECONDS, deliverCommitHooks, describeHookEnd, execArgv, execCommandLine, finishJob, hookLimitsFor, hostCommandEnvironment, hostEnvironment, isGroupAlive, isIdle, isTimedOut, isUnfinished, jobEnv, listAdoptableJobs, listenOnBroker, microVmCommandLine, needsRenew, outcomeFromExitCode, provisionEnvironment, provisionMicroVm, provisionProcess, readCpuUsec, readProvisionOut, recoverLostJobs, releaseLease, renewLease, resolveHostBuild, retryFailedReleases, runCommand, runHook, setStage, startHostServices, startSender, stopOrphanedMicroVms, unfinishedJobs, workdirProblem };
|