@outerlayer/cli 0.5.1 → 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 +11 -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-4Q33B5ZE.js → check-CCVPBN7A.js} +24 -14
- package/dist/{chunk-WOBNRVQT.js → chunk-3DEVYTNQ.js} +1 -1
- package/dist/{chunk-F4NU4H3V.js → chunk-43J3M6KB.js} +95 -11
- package/dist/{chunk-W7UTFZDH.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-ZXBITXTS.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-H4CK2YK4.js → chunk-MIGEDH5P.js} +16 -8
- package/dist/{chunk-CHLVJNTE.js → chunk-MXB5VB4Y.js} +17 -5
- 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-RTXKYLCA.js → chunk-RBOMWNOG.js} +1 -1
- package/dist/{chunk-T52PKE2Y.js → chunk-RZSF5B46.js} +149 -7
- package/dist/chunk-T6OIJQRT.js +31 -0
- package/dist/{chunk-EGABNHZG.js → chunk-UMFWVGJO.js} +148 -18
- package/dist/{chunk-UWH5DSUP.js → chunk-VMJ3MMBX.js} +203 -80
- package/dist/chunk-ZILGTTY6.js +75 -0
- package/dist/{cli-LHSUYYMI.js → cli-7WCNPBYC.js} +134 -61
- package/dist/command-ARFK3OYF.js +162 -0
- 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-MOA3RIEP.js → doctor-VOMYOHQU.js} +32 -12
- 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-2IPOIXJV.js → init-OHYFIKZD.js} +26 -55
- package/dist/{init-FYDJAKTM.js → init-WVR4BZQ4.js} +8 -8
- package/dist/{init-cmd-KLIDRVXQ.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-NEBUGD2Q.js → loop-UAJB34KA.js} +24 -11
- package/dist/{machine-ECU3QCZD.js → machine-2JOWGRSZ.js} +7 -7
- package/dist/session-sender-ED26V45B.js +28 -0
- package/dist/{status-CLATHCTQ.js → status-6WNXX2O7.js} +22 -8
- 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-3OPWA77E.js → work-claim-cmd-LXBZTMJA.js} +5 -5
- 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
|
@@ -7,23 +7,23 @@ import { createDockerHost, childEnvironment, spawnRun } from './chunk-774HEQL6.j
|
|
|
7
7
|
import { RecipeError } from './chunk-QOZKPLVJ.js';
|
|
8
8
|
import { execCapture } from './chunk-W4SQCOZU.js';
|
|
9
9
|
import { GUEST_CID } from './chunk-B2G7JHHB.js';
|
|
10
|
-
import { WorkCommandError, postJson } from './chunk-
|
|
10
|
+
import { WorkCommandError, postJson } from './chunk-BNMCXPG7.js';
|
|
11
11
|
import { parseDuKilobytes, hostAllowed } from './chunk-F6GFZXZ3.js';
|
|
12
12
|
import { RELAY_PORT, CHECKOUT_DIR, JOB_MOUNT, claudeCredential, buildEnvironment, startBuildServices, AGENT_CREDENTIAL_MISSING, GATEWAY_PORT, GATEWAY_SOCKET, CLAUDE_PORT, CLAUDE_SOCKET, DESTINATION_BASE_PORT } from './chunk-AUFG23AA.js';
|
|
13
|
-
import { defaultGitRunner, materializeContext } from './chunk-
|
|
13
|
+
import { defaultGitRunner, materializeContext } from './chunk-OIRNH6UO.js';
|
|
14
14
|
import { readContextManifest, readSourceCache } from './chunk-WIOZAJ2W.js';
|
|
15
15
|
import { signingFetchFor } from './chunk-BCLJSQCV.js';
|
|
16
16
|
import { signerFor } from './chunk-M5POMKKW.js';
|
|
17
17
|
import { rootfsPathFor, diskTemplatesDir, RUNNER_FILE_MODE, stagedContextDir, contextHomeDir, ghConfigDir, runnerDirOfBuild, spoolDir, mirrorDir, buildDir, runnerDir, RUNNER_DIR_MODE, vmAssetsDir } from './chunk-ZM2IMMYN.js';
|
|
18
18
|
import { relative, join, dirname, resolve, basename } from 'path';
|
|
19
|
+
import { readFileSync, existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, accessSync, constants, readdirSync, statSync, rmdirSync, realpathSync, renameSync, lstatSync, unlinkSync, openSync, closeSync, mkdtempSync, createWriteStream, createReadStream } from 'fs';
|
|
19
20
|
import { spawnSync, spawn } from 'child_process';
|
|
20
21
|
import { createHash, randomBytes } from 'crypto';
|
|
21
|
-
import { existsSync, mkdirSync, chmodSync, truncateSync, rmSync, writeFileSync, realpathSync, renameSync, lstatSync, readFileSync, unlinkSync, readdirSync, openSync, closeSync, mkdtempSync, createWriteStream, statSync, createReadStream } from 'fs';
|
|
22
22
|
import { createServer, request } from 'http';
|
|
23
23
|
import { request as request$1 } from 'https';
|
|
24
24
|
import { Transform } from 'stream';
|
|
25
25
|
import { pipeline, finished } from 'stream/promises';
|
|
26
|
-
import { createGunzip, gunzipSync, constants } from 'zlib';
|
|
26
|
+
import { createGunzip, gunzipSync, constants as constants$1 } from 'zlib';
|
|
27
27
|
import { isIP, connect, createServer as createServer$1 } from 'net';
|
|
28
28
|
import { lookup } from 'dns/promises';
|
|
29
29
|
import { networkInterfaces } from 'os';
|
|
@@ -255,6 +255,111 @@ var BuildTokens = class {
|
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
};
|
|
258
|
+
var BUILD_PREFIX = "build-";
|
|
259
|
+
var LEAF = "runner";
|
|
260
|
+
function serviceDirectory(selfCgroup, mount) {
|
|
261
|
+
const line = selfCgroup?.split("\n").find((l) => l.startsWith("0::"));
|
|
262
|
+
if (line === void 0) return void 0;
|
|
263
|
+
const path = line.slice(3).trim();
|
|
264
|
+
if (!path.endsWith(".service")) return void 0;
|
|
265
|
+
return join(mount, path);
|
|
266
|
+
}
|
|
267
|
+
function prepareRunnerCgroup(setup) {
|
|
268
|
+
if (setup.invocationId === void 0 || setup.invocationId === "") return void 0;
|
|
269
|
+
const root = serviceDirectory(setup.selfCgroup, setup.mount);
|
|
270
|
+
if (root === void 0) return void 0;
|
|
271
|
+
const { fs } = setup;
|
|
272
|
+
if (!fs.exists(root) || !fs.writable(join(root, "cgroup.procs"))) return void 0;
|
|
273
|
+
if (!(fs.read(join(root, "cgroup.controllers")) ?? "").split(/\s+/).includes("memory")) return void 0;
|
|
274
|
+
for (const name of fs.list(root)) {
|
|
275
|
+
if (!name.startsWith(BUILD_PREFIX)) continue;
|
|
276
|
+
try {
|
|
277
|
+
fs.rmdir(join(root, name));
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!fs.exists(join(root, LEAF))) fs.mkdir(join(root, LEAF));
|
|
282
|
+
fs.write(join(root, LEAF, "cgroup.procs"), String(setup.pid));
|
|
283
|
+
fs.write(join(root, "cgroup.subtree_control"), "+memory");
|
|
284
|
+
return { root };
|
|
285
|
+
}
|
|
286
|
+
function createBuildCgroup(fs, root, buildId, buildMemoryBytes) {
|
|
287
|
+
const path = join(root, `${BUILD_PREFIX}${buildId}`);
|
|
288
|
+
const firecrackerOverheadBytes = 256 * 1024 ** 2;
|
|
289
|
+
fs.mkdir(path);
|
|
290
|
+
try {
|
|
291
|
+
fs.write(join(path, "memory.max"), String(buildMemoryBytes + firecrackerOverheadBytes));
|
|
292
|
+
fs.write(join(path, "memory.swap.max"), "0");
|
|
293
|
+
} catch (err) {
|
|
294
|
+
removeBuildCgroup(fs, path);
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
return path;
|
|
298
|
+
}
|
|
299
|
+
function oomKills(fs, path) {
|
|
300
|
+
const match = /^oom_kill (\d+)$/m.exec(fs.read(join(path, "memory.events")) ?? "");
|
|
301
|
+
return match === null ? 0 : Number(match[1]);
|
|
302
|
+
}
|
|
303
|
+
function removeBuildCgroup(fs, path) {
|
|
304
|
+
try {
|
|
305
|
+
fs.rmdir(path);
|
|
306
|
+
} catch (err) {
|
|
307
|
+
if (err.code !== "ENOENT") throw err;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function realCgroupFs() {
|
|
311
|
+
return {
|
|
312
|
+
exists: (path) => existsSync(path),
|
|
313
|
+
read: (path) => {
|
|
314
|
+
try {
|
|
315
|
+
return readFileSync(path, "utf8");
|
|
316
|
+
} catch {
|
|
317
|
+
return void 0;
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
write: (path, data) => writeFileSync(path, data),
|
|
321
|
+
mkdir: (path) => mkdirSync(path),
|
|
322
|
+
rmdir: (path) => rmdirSync(path),
|
|
323
|
+
list: (path) => readdirSync(path).filter((name) => statSync(join(path, name)).isDirectory()),
|
|
324
|
+
writable: (path) => {
|
|
325
|
+
try {
|
|
326
|
+
accessSync(path, constants.W_OK);
|
|
327
|
+
return true;
|
|
328
|
+
} catch {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
var delegatedRoot;
|
|
335
|
+
function setUpRunnerCgroup(over = {}) {
|
|
336
|
+
let selfCgroup;
|
|
337
|
+
try {
|
|
338
|
+
selfCgroup = readFileSync("/proc/self/cgroup", "utf8");
|
|
339
|
+
} catch {
|
|
340
|
+
selfCgroup = void 0;
|
|
341
|
+
}
|
|
342
|
+
let refusal;
|
|
343
|
+
try {
|
|
344
|
+
const prepared = prepareRunnerCgroup({ fs: realCgroupFs(), selfCgroup, pid: process.pid, invocationId: process.env.INVOCATION_ID, mount: "/sys/fs/cgroup", ...over });
|
|
345
|
+
delegatedRoot = prepared?.root;
|
|
346
|
+
} catch (err) {
|
|
347
|
+
delegatedRoot = void 0;
|
|
348
|
+
refusal = err instanceof Error ? err.message : String(err);
|
|
349
|
+
}
|
|
350
|
+
return { delegated: delegatedRoot !== void 0, ...refusal === void 0 ? {} : { refusal } };
|
|
351
|
+
}
|
|
352
|
+
function realCgroupPort() {
|
|
353
|
+
const fs = realCgroupFs();
|
|
354
|
+
return {
|
|
355
|
+
create: (buildId, bytes) => {
|
|
356
|
+
if (delegatedRoot === void 0) return void 0;
|
|
357
|
+
return createBuildCgroup(fs, delegatedRoot, buildId, bytes);
|
|
358
|
+
},
|
|
359
|
+
oomKills: (path) => oomKills(fs, path),
|
|
360
|
+
remove: (path) => removeBuildCgroup(fs, path)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
258
363
|
var BranchPrepareError = class extends Error {
|
|
259
364
|
};
|
|
260
365
|
var OWN_PREFIX = "outerlayer/";
|
|
@@ -747,7 +852,9 @@ function machineConfig(limits2, paths) {
|
|
|
747
852
|
};
|
|
748
853
|
}
|
|
749
854
|
function firecrackerCommand(input) {
|
|
750
|
-
|
|
855
|
+
const args = ["--no-api", "--config-file", input.configPath];
|
|
856
|
+
if (input.cgroup === void 0) return { command: input.binary, args };
|
|
857
|
+
return { command: "sh", args: ["-c", 'echo $$ > "$1/cgroup.procs" && shift && exec "$@"', "sh", input.cgroup, input.binary, ...args] };
|
|
751
858
|
}
|
|
752
859
|
var STOP_WAIT_MS = 1e4;
|
|
753
860
|
var POLL_MS = 20;
|
|
@@ -804,6 +911,7 @@ function createMicroVmPorts(over = {}) {
|
|
|
804
911
|
assets: over.assets ?? ((runnerDir2) => ensureAssets({ runnerDir: runnerDir2 })),
|
|
805
912
|
rootfs: over.rootfs ?? createRootfsHost(),
|
|
806
913
|
firecracker: over.firecracker ?? createFirecrackerHost(),
|
|
914
|
+
cgroup: over.cgroup ?? realCgroupPort(),
|
|
807
915
|
exec: over.exec ?? execCapture
|
|
808
916
|
};
|
|
809
917
|
}
|
|
@@ -1093,7 +1201,7 @@ function decode(prefix, gzip) {
|
|
|
1093
1201
|
if (!gzip) return prefix;
|
|
1094
1202
|
try {
|
|
1095
1203
|
const { compressedWindow, inflatedBytes } = limits();
|
|
1096
|
-
return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
|
|
1204
|
+
return gunzipSync(prefix.subarray(0, compressedWindow), { finishFlush: constants$1.Z_SYNC_FLUSH, maxOutputLength: inflatedBytes });
|
|
1097
1205
|
} catch (err) {
|
|
1098
1206
|
const code = err.code;
|
|
1099
1207
|
if (code === "ERR_BUFFER_TOO_LARGE") return refused(TOO_LONG);
|
|
@@ -2149,6 +2257,40 @@ function containerCommandLine(input) {
|
|
|
2149
2257
|
env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed }
|
|
2150
2258
|
};
|
|
2151
2259
|
}
|
|
2260
|
+
function containerSenderCommand(input) {
|
|
2261
|
+
const state = input.job.container;
|
|
2262
|
+
const env = buildEnvironment({
|
|
2263
|
+
config: input.config,
|
|
2264
|
+
item: input.job.item,
|
|
2265
|
+
itemKey: input.job.itemKey,
|
|
2266
|
+
branch: input.job.branch,
|
|
2267
|
+
repository: state.repository,
|
|
2268
|
+
hostEnv: input.runtime.hostEnv,
|
|
2269
|
+
containerEnv: state.containerEnv ?? {},
|
|
2270
|
+
builtIns: state.builtIns === true,
|
|
2271
|
+
identity: input.job.commitIdentity
|
|
2272
|
+
});
|
|
2273
|
+
const args = containerExecArgs({
|
|
2274
|
+
container: state.container,
|
|
2275
|
+
user: state.agentUser ?? ADDED_USER,
|
|
2276
|
+
workdir: CHECKOUT_DIR,
|
|
2277
|
+
env,
|
|
2278
|
+
argv: ["outerlayer", "session-sender"]
|
|
2279
|
+
});
|
|
2280
|
+
return { command: "docker", args: ["exec", ...args], env: { ...childEnvironment(input.runtime.hostEnv), ...env.passed } };
|
|
2281
|
+
}
|
|
2282
|
+
function senderSignalScript(pidfile, signal) {
|
|
2283
|
+
const file = shellQuote(pidfile);
|
|
2284
|
+
return `p="$(cat ${file})" && [ -n "$p" ] && grep -qa session-sender "/proc/$p/cmdline" && kill -${signal} "$p"`;
|
|
2285
|
+
}
|
|
2286
|
+
async function signalInContainer(runtime, state, pidfile, signal) {
|
|
2287
|
+
await runtime.host.exec({
|
|
2288
|
+
container: state.container,
|
|
2289
|
+
user: state.agentUser ?? ADDED_USER,
|
|
2290
|
+
env: NO_ENV,
|
|
2291
|
+
argv: ["/bin/sh", "-c", senderSignalScript(pidfile, signal)]
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2152
2294
|
async function stopContainer(runtime, state) {
|
|
2153
2295
|
await runtime.host.stop(state.container);
|
|
2154
2296
|
state.stopped = true;
|
|
@@ -2247,4 +2389,4 @@ async function cleanupContainer(runtime, state, jobDir) {
|
|
|
2247
2389
|
if (failures.length > 0) throw new Error(failures.join("; "));
|
|
2248
2390
|
}
|
|
2249
2391
|
|
|
2250
|
-
export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, shellQuote, shortClaim, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
|
|
2392
|
+
export { ADDED_USER, AgentUserError, AssetError, BUILD_LABEL, BuildTokens, GH_CONFIG_YML, MIN_ENGINE_MAJOR, RUNNER_LABEL, classifyRefusal, cleanupContainer, containerCommandLine, containerSenderCommand, createBuildDisk, createContainerRuntime, createRunnerLog, deleteBuildDisk, describeCallFailure, ensureRootfs, ensureTunnel, formatClock, formatDuration, ghHostsYml, gitRemoteFor, isLiteralAddress, keepGhConfigCurrent, lifecycleFailureReason, lifecycleSteps, machineConfig, measureDisk, obtainImage, prepareBranch, provisionContainer, refusalCode, relativeToRunner, removeStagedContext, resolveAgentUser, retrySend, runBuildSteps, runnerLabelValue, senderSignalScript, setUpRunnerCgroup, shellQuote, shortClaim, signalInContainer, stageGovernedContext, startServicesFor, stopContainer, syncInContainer };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-ZM2IMMYN.js';
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
createRequire(import.meta.url);
|
|
8
|
+
function readJobFile(path) {
|
|
9
|
+
if (!existsSync(path)) return null;
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function readJobFileChecked(path) {
|
|
17
|
+
if (!existsSync(path)) return { kind: "missing" };
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { kind: "unreadable" };
|
|
21
|
+
return { kind: "ok", job: parsed };
|
|
22
|
+
} catch {
|
|
23
|
+
return { kind: "unreadable" };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeJobFile(path, job) {
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
28
|
+
writeFileSync(path, JSON.stringify(job, null, 2), { mode: RUNNER_FILE_MODE });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { readJobFile, readJobFileChecked, writeJobFile };
|
|
@@ -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 };
|