@outerlayer/cli 0.17.0 → 0.18.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 +47 -0
- package/README.md +15 -0
- package/dist/agent-setup-RBTEACYU.js +28 -0
- package/dist/{artifact-link-cmd-2F47LT7F.js → artifact-link-cmd-AVM2EKTV.js} +4 -3
- package/dist/{build-BG2YENGK.js → build-TQ6X5PYN.js} +5 -4
- package/dist/build-info.json +1 -1
- package/dist/{builder-2D3WSOOY.js → builder-DUI2ZH2H.js} +5 -6
- package/dist/{check-5XE7XMC4.js → check-5C4YDZYV.js} +53 -50
- package/dist/{chunk-R7PF5OOU.js → chunk-3CWUOFV6.js} +21 -14
- package/dist/{chunk-G5NPRL2K.js → chunk-3OFZM4X6.js} +548 -68
- package/dist/{chunk-2MNPHL23.js → chunk-47TTLEDX.js} +6 -5
- package/dist/{chunk-PLMTLGYH.js → chunk-52NC2FIU.js} +4 -3
- package/dist/chunk-6CHIYEZZ.js +564 -0
- package/dist/{chunk-TI7B7UEO.js → chunk-AMFWYESY.js} +1 -1
- package/dist/{chunk-YRYMNBGQ.js → chunk-ANPNT6PM.js} +2 -1
- package/dist/{chunk-YU3VKZ5F.js → chunk-B3UKB46N.js} +6 -6
- package/dist/{chunk-JXYVA45C.js → chunk-BMYGAGBJ.js} +1050 -304
- package/dist/{chunk-4Q7OMKAT.js → chunk-BQXFBI7T.js} +81 -5
- package/dist/chunk-CRB3POP4.js +34 -0
- package/dist/{chunk-F47JBIAW.js → chunk-DSF2FWAZ.js} +2 -1
- package/dist/{chunk-N5FOE5PS.js → chunk-ECODQ7II.js} +4 -33
- package/dist/{chunk-NBGDBTO2.js → chunk-ESC6UR76.js} +6 -5
- package/dist/{chunk-HHJDN6AU.js → chunk-ESVW4MJY.js} +1 -1
- package/dist/{chunk-42ZI52JW.js → chunk-F4XSVQJ4.js} +4 -3
- package/dist/{chunk-LWSJQJC6.js → chunk-FUB7LRUE.js} +7 -7
- package/dist/chunk-G2YNJIGF.js +44 -0
- package/dist/{chunk-QKFB4K7X.js → chunk-GGOMT6KF.js} +1 -1
- package/dist/{chunk-VQYBXPI7.js → chunk-GOBOJMOJ.js} +5 -5
- package/dist/{chunk-N6LURUEF.js → chunk-GOSFARMS.js} +1 -1
- package/dist/chunk-JBJTC3UU.js +35 -0
- package/dist/{chunk-NWQTTSBB.js → chunk-JF42URPU.js} +16 -15
- package/dist/{chunk-ADJRGVZC.js → chunk-KDVIU23F.js} +1 -1
- package/dist/{chunk-S3NYLI2P.js → chunk-KK4VBOH6.js} +93 -37
- package/dist/{chunk-KMDLFI4P.js → chunk-L2USZT6T.js} +1 -1
- package/dist/{chunk-WTE3MQML.js → chunk-LXAY4A2G.js} +3 -12
- package/dist/{chunk-V7NIVOFD.js → chunk-M2ZWWTWZ.js} +1 -1
- package/dist/{chunk-ERI5GBNI.js → chunk-MLAAT4S6.js} +3 -3
- package/dist/{chunk-POJMEEJJ.js → chunk-MNTSH4CL.js} +1 -1
- package/dist/{chunk-WUYTWNMA.js → chunk-MQJSXHV7.js} +1 -1
- package/dist/{chunk-O765JR4W.js → chunk-MTP2QEVO.js} +1 -1
- package/dist/{chunk-ZM2IMMYN.js → chunk-N2PXGQRL.js} +9 -1
- package/dist/{chunk-J5M7BSJQ.js → chunk-N3CUDFCW.js} +9 -3
- package/dist/{chunk-4KSQKQXE.js → chunk-NACDBXUG.js} +19 -15
- package/dist/{chunk-ONKJUM6R.js → chunk-NL4XPC7W.js} +1 -1
- package/dist/{chunk-WVUIRTWA.js → chunk-QQM2UJ6T.js} +31 -26
- package/dist/{chunk-KG5ECZGJ.js → chunk-RAZTWYZN.js} +10 -9
- package/dist/{chunk-4NPZVMJU.js → chunk-S26M3YBJ.js} +60 -4
- package/dist/{chunk-UGP76IZ5.js → chunk-S5PCA3SN.js} +1 -1
- package/dist/{chunk-3NSNIIBE.js → chunk-S76AYB4M.js} +17 -16
- package/dist/{chunk-RMWLBUCJ.js → chunk-SADUQSO4.js} +1 -1
- package/dist/{chunk-OZGHL5Y5.js → chunk-SWCTGCSC.js} +2 -2
- package/dist/{chunk-GQD22KKN.js → chunk-T2Q7ISPE.js} +2 -1
- package/dist/{chunk-C5Y5ZJJZ.js → chunk-T4Y6SCG3.js} +5 -4
- package/dist/{chunk-BWQ66M6N.js → chunk-ULSIJGLS.js} +45 -13
- package/dist/{chunk-NNRTPG7X.js → chunk-UYOYTKE2.js} +4 -4
- package/dist/{chunk-PTW6W5NR.js → chunk-VYAL7PUE.js} +1 -1
- package/dist/{chunk-YZ5F4PIS.js → chunk-W5VPCONO.js} +146 -247
- package/dist/{chunk-6Y5RUTHO.js → chunk-WBT5X3NH.js} +5 -4
- package/dist/{chunk-RLYZLUQ7.js → chunk-WGAUY5LE.js} +2 -2
- package/dist/{chunk-RSNSZ2LJ.js → chunk-X5H2WYMB.js} +1 -1
- package/dist/{chunk-OHVJA253.js → chunk-X5N6KC6V.js} +47 -29
- package/dist/{chunk-22RMZBGW.js → chunk-XGP3VYOG.js} +1 -1
- package/dist/{chunk-HYZRY4KZ.js → chunk-XNGMQ27R.js} +16 -6
- package/dist/{chunk-HGECYFEI.js → chunk-Y5GE76XC.js} +131 -54
- package/dist/{chunk-MAK5LYRF.js → chunk-Y5MXET4M.js} +277 -535
- package/dist/{chunk-XCK2YAY5.js → chunk-YVZJ6GKF.js} +3 -2
- package/dist/{chunk-YO4PSLVB.js → chunk-ZRL6HKBY.js} +1 -1
- package/dist/claude-launcher-YVXI76NX.js +102 -0
- package/dist/{cli-A76AXELQ.js → cli-C77H54BV.js} +468 -161
- package/dist/{command-R2QCVX55.js → command-H5CILO4S.js} +40 -37
- package/dist/{config-N3C6DFQA.js → config-FXDG4NPJ.js} +2 -3
- package/dist/{connect-cmd-JBKAQNSF.js → connect-cmd-ZW5FV3ZW.js} +9 -8
- package/dist/{context-adopt-NFHCLVUB.js → context-adopt-O5OFXMKI.js} +13 -12
- package/dist/context-materialize-Y5IJ4MQT.js +23 -0
- package/dist/{dist-LKDWQWZ7.js → dist-QTX4RSCL.js} +2 -2
- package/dist/{docker-4J77Q2BF.js → docker-RC3PRX3Q.js} +7 -8
- package/dist/{doctor-4TAIARIG.js → doctor-2NQQML6P.js} +46 -39
- package/dist/{emit-artifact-cmd-MPZVR56N.js → emit-artifact-cmd-7MP63KXU.js} +25 -22
- package/dist/emit-cmd-PPN4QQF2.js +14 -0
- package/dist/{emit-criteria-cmd-EFGYI23V.js → emit-criteria-cmd-3RXPDLNK.js} +16 -14
- package/dist/{emit-finding-cmd-5GF3V4LR.js → emit-finding-cmd-OL7JB6H3.js} +18 -16
- package/dist/{emit-result-cmd-I4OADKMD.js → emit-result-cmd-UZVMOCF4.js} +20 -17
- package/dist/{exec-client-CQV746UF.js → exec-client-7UMESIE2.js} +3 -4
- package/dist/governing-source-BUU4IABS.js +35 -0
- package/dist/guest-init-KPBQOFE5.js +9 -0
- package/dist/{hook-fast-R4FVSZ2H.js → hook-fast-W5MKHMJT.js} +4 -1
- package/dist/host-key-U4B5QETR.js +8 -0
- package/dist/{import-capture-cmd-EQ35EARA.js → import-capture-cmd-ZO6KGHI2.js} +7 -5
- package/dist/{import-ruler-cmd-BGAZXCQM.js → import-ruler-cmd-KJMWL5V6.js} +5 -3
- package/dist/index.js +8 -4
- package/dist/init-VWXIZEAN.js +54 -0
- package/dist/{init-PWU5YVL7.js → init-WNXXYMO7.js} +38 -35
- package/dist/{init-cmd-XAVO7HYZ.js → init-cmd-WS6TIRYT.js} +28 -24
- package/dist/{init-update-CVCWD6VS.js → init-update-E3J6TVIN.js} +27 -26
- package/dist/{install-HDBIPHTX.js → install-CBWIEF3R.js} +4 -5
- package/dist/{install-cmd-5HYMXLEW.js → install-cmd-QDOTR4OM.js} +5 -4
- package/dist/{login-browser-QZCS3WW6.js → login-browser-O63QZN7D.js} +4 -3
- package/dist/{logout-cmd-QAUFDJ6J.js → logout-cmd-5V5MMBSY.js} +3 -2
- package/dist/{logs-H55NWW4H.js → logs-AMLKC3VP.js} +3 -1
- package/dist/loop-UGI2BJOT.js +76 -0
- package/dist/machine-JOW4RNZ7.js +49 -0
- package/dist/{mcp-install-cmd-FDTPQQS6.js → mcp-install-cmd-GGWB3JJ2.js} +3 -2
- package/dist/{mcp-serve-cmd-3KVYEUXG.js → mcp-serve-cmd-DOTY5FKY.js} +10 -8
- package/dist/paths-7XP63OAX.js +7 -0
- package/dist/pending-532ABZYC.js +8 -0
- package/dist/{pidfile-UZRH774M.js → pidfile-3DM4OBKO.js} +4 -2
- package/dist/{record-C43U4PKS.js → record-3WP6V2K5.js} +3 -2
- package/dist/{runner-env-EN7YE7QX.js → runner-env-4NB2GA7O.js} +5 -6
- package/dist/{runner-key-6SLBI7PP.js → runner-key-TC3NFWFV.js} +38 -35
- package/dist/{session-sender-FO3LCREV.js → session-sender-NNVPSPY7.js} +17 -14
- package/dist/spec-cmd-OWVTBB4J.js +116 -0
- package/dist/{starter-pack-KF66HGZR.js → starter-pack-OPWUDOWX.js} +7 -5
- package/dist/{status-7VNQN5PW.js → status-TKCLLKFG.js} +42 -39
- package/dist/{statusline-fast-SVTMBMD7.js → statusline-fast-KN3BX67H.js} +2 -1
- package/dist/{sync-cmd-GVPPFZ3G.js → sync-cmd-Q6VYNPWG.js} +16 -13
- package/dist/{watch-23HSAEI6.js → watch-ARAGADTF.js} +16 -13
- package/dist/{work-claim-cmd-LKL3GHFQ.js → work-claim-cmd-34FERLYX.js} +25 -27
- package/dist/work-cmd-7S5B2H2P.js +25 -0
- package/dist/{work-comment-cmd-AWHP56M2.js → work-comment-cmd-SZJSPM5S.js} +15 -13
- package/dist/{work-launch-R5HTAP6X.js → work-launch-LEQ2MPM3.js} +3 -1
- package/dist/{work-open-pr-cmd-IOM4AVQQ.js → work-open-pr-cmd-RPXKCBWS.js} +17 -15
- package/dist/work-pr-cmd-5LGANL32.js +26 -0
- package/package.json +3 -2
- package/skill-pack/maintained/outerlayer-amend/SKILL.md +4 -2
- package/skill-pack/shipped.json +17 -2
- package/skill-pack/template/build/SKILL.md +23 -10
- package/skill-pack/template/build/references/agent-briefs.md +3 -1
- package/skill-pack/template/build/references/criteria-judge.md +27 -0
- package/skill-pack/template/build/references/release.md +19 -28
- package/skill-pack/template/spec/SKILL.md +35 -10
- package/skill-pack/validators/acceptance-criteria.yaml +25 -26
- package/dist/agent-setup-T624CWPD.js +0 -27
- package/dist/chunk-BYT5O5RV.js +0 -61
- package/dist/context-materialize-IV2TUUFO.js +0 -22
- package/dist/emit-cmd-IJ3GVRFD.js +0 -12
- package/dist/guest-init-LJP5GPZF.js +0 -9
- package/dist/host-key-YMLXY6QB.js +0 -7
- package/dist/init-6XPDRDTC.js +0 -51
- package/dist/loop-FK3LPMMZ.js +0 -73
- package/dist/machine-SQXEBAM2.js +0 -46
- package/dist/paths-OYKMVYJP.js +0 -6
- package/dist/pending-MXLSRYMO.js +0 -7
- package/dist/work-cmd-Z2WXLZPH.js +0 -23
- package/dist/work-pr-cmd-HNEUIEN7.js +0 -24
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { C } from './chunk-
|
|
4
|
-
import { retiredSkillPaths, removeRetiredSkills } from './chunk-
|
|
5
|
-
import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-
|
|
6
|
-
import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-
|
|
3
|
+
import { C } from './chunk-ANPNT6PM.js';
|
|
4
|
+
import { retiredSkillPaths, removeRetiredSkills } from './chunk-T4Y6SCG3.js';
|
|
5
|
+
import { maintainedFilesForExistingSkills, loadMaintainedFiles, writeMaintainedFiles } from './chunk-MNTSH4CL.js';
|
|
6
|
+
import { emitTree, parseOuterlayerConfig, classifyTree, generatedHeaderSource, CONTEXT_PIN_FILENAME, normalizePath } from './chunk-X5N6KC6V.js';
|
|
7
|
+
import { cliInvocation } from './chunk-CRB3POP4.js';
|
|
7
8
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, copyFileSync, readdirSync } from 'fs';
|
|
8
9
|
import { join, dirname, relative, sep } from 'path';
|
|
9
10
|
|
|
@@ -97,7 +98,7 @@ function renderFailure(errors) {
|
|
|
97
98
|
}
|
|
98
99
|
function renderCheck(targets, drift) {
|
|
99
100
|
if (drift.length === 0) return `${C.green}\u2713${C.reset} up to date for ${C.dim}${targets.join(", ")}${C.reset}`;
|
|
100
|
-
const lines = [`${C.red}\u2717${C.reset} drift detected (${drift.length}) \u2014 run
|
|
101
|
+
const lines = [`${C.red}\u2717${C.reset} drift detected (${drift.length}) \u2014 run \`${cliInvocation()} emit\` to update`];
|
|
101
102
|
for (const d of drift) lines.push(` ${C.red}\u2717${C.reset} ${d.kind.padEnd(8)} ${d.path}`);
|
|
102
103
|
return lines.join("\n");
|
|
103
104
|
}
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { readCloudConfig } from './chunk-EABW6AJQ.js';
|
|
4
4
|
import { readRepoConnection, findGitDirs } from './chunk-BJ3KTMDH.js';
|
|
5
|
+
import { cliInvocation } from './chunk-CRB3POP4.js';
|
|
5
6
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
6
7
|
|
|
7
8
|
createRequire(import.meta.url);
|
|
8
9
|
|
|
9
10
|
// src/credentials.ts
|
|
10
|
-
var MISSING_HELP =
|
|
11
|
-
var NO_FACTORY_MESSAGE =
|
|
11
|
+
var MISSING_HELP = `Run \`${cliInvocation()} login\` to sign in, or set OUTERLAYER_URL / OUTERLAYER_API_KEY / OUTERLAYER_APP_ID. Mint a key in the dashboard: Settings \u2192 API keys.`;
|
|
12
|
+
var NO_FACTORY_MESSAGE = `you are logged in, but no factory is chosen for this repository. Run \`${cliInvocation()} connect\` to choose one, or pass --app-id / set OUTERLAYER_APP_ID.`;
|
|
12
13
|
var NO_FACTORY_OUTSIDE_REPOSITORY_MESSAGE = "you are logged in, but this directory is not in a git repository, so no factory is chosen. Pass --app-id / set OUTERLAYER_APP_ID, or run this from inside a connected repository.";
|
|
13
14
|
var LOGIN_TOKEN_PREFIX = "olu_";
|
|
14
15
|
function unauthorizedMessage(credential) {
|
|
15
|
-
return credential.startsWith(LOGIN_TOKEN_PREFIX) ?
|
|
16
|
+
return credential.startsWith(LOGIN_TOKEN_PREFIX) ? `not authorized \u2014 the login is expired or was revoked. Run \`${cliInvocation()} login\` to sign in again.` : "not authorized \u2014 the API key is unknown, expired, or bound to a different factory. Mint a fresh key: dashboard \u2192 Settings \u2192 API keys.";
|
|
16
17
|
}
|
|
17
18
|
function resolveGatewayCredentials(sources = {}) {
|
|
18
19
|
const env = sources.env ?? process.env;
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { WorkCommandError, postJson } from './chunk-KK4VBOH6.js';
|
|
4
|
+
import { defaultGitRunner, materializeContext, isStagingWriteFault, lookupGoverningSource } from './chunk-W5VPCONO.js';
|
|
5
|
+
import { readDegraded, readContextManifest, readSourceCache } from './chunk-LXAY4A2G.js';
|
|
6
|
+
import { signingFetchFor } from './chunk-ZRL6HKBY.js';
|
|
7
|
+
import { MAINTAINED_SKILLS } from './chunk-MNTSH4CL.js';
|
|
8
|
+
import { signerFor } from './chunk-M2ZWWTWZ.js';
|
|
9
|
+
import { SHIM_DIR, stagedContextDir, contextHomeDir, deliveryDir } from './chunk-N2PXGQRL.js';
|
|
10
|
+
import { SHIM_SCRIPT, rewriteControlPlanePaths, rewriteHookCommand } from './chunk-G2YNJIGF.js';
|
|
11
|
+
import { fstatSync, existsSync, readdirSync, mkdirSync, writeFileSync, chmodSync, rmSync, copyFileSync, lstatSync, readFileSync } from 'fs';
|
|
12
|
+
import { relative, join, dirname } from 'path';
|
|
13
|
+
|
|
14
|
+
createRequire(import.meta.url);
|
|
15
|
+
function logStamp(at) {
|
|
16
|
+
return at.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
17
|
+
}
|
|
18
|
+
function sameDestination(stat = fstatSync) {
|
|
19
|
+
try {
|
|
20
|
+
const out = stat(1);
|
|
21
|
+
const err = stat(2);
|
|
22
|
+
return out.dev === err.dev && out.ino === err.ino;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function createRunnerLog(now = () => /* @__PURE__ */ new Date(), io = { stdout: process.stdout, stderr: process.stderr, joined: () => sameDestination() }) {
|
|
28
|
+
const write = (stream, text) => {
|
|
29
|
+
stream.write(`${logStamp(now())} ${text}
|
|
30
|
+
`);
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
line: (text) => write(io.stdout, text),
|
|
34
|
+
error: (text) => {
|
|
35
|
+
write(io.stdout, text);
|
|
36
|
+
if (!io.joined()) write(io.stderr, text);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function formatDuration(ms) {
|
|
41
|
+
const total = Math.max(0, Math.round(ms / 1e3));
|
|
42
|
+
const hours = Math.floor(total / 3600);
|
|
43
|
+
const minutes = Math.floor(total % 3600 / 60);
|
|
44
|
+
const seconds = total % 60;
|
|
45
|
+
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
46
|
+
if (minutes > 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
47
|
+
return `${seconds}s`;
|
|
48
|
+
}
|
|
49
|
+
function formatClock(at, today) {
|
|
50
|
+
const hhmm = `${String(at.getUTCHours()).padStart(2, "0")}:${String(at.getUTCMinutes()).padStart(2, "0")} UTC`;
|
|
51
|
+
const sameDay = at.getUTCFullYear() === today.getUTCFullYear() && at.getUTCMonth() === today.getUTCMonth() && at.getUTCDate() === today.getUTCDate();
|
|
52
|
+
if (sameDay) return hhmm;
|
|
53
|
+
const month = at.toLocaleString("en-US", { month: "short", timeZone: "UTC" });
|
|
54
|
+
return `${month} ${at.getUTCDate()} ${hhmm}`;
|
|
55
|
+
}
|
|
56
|
+
function shortClaim(claimId) {
|
|
57
|
+
return claimId.length <= 8 ? claimId : `${claimId.slice(0, 8)}\u2026`;
|
|
58
|
+
}
|
|
59
|
+
function relativeToRunner(runnerDirPath, path) {
|
|
60
|
+
const rel = relative(runnerDirPath, path);
|
|
61
|
+
return rel === "" || rel.startsWith("..") ? path : rel;
|
|
62
|
+
}
|
|
63
|
+
function describeCallFailure(err) {
|
|
64
|
+
if (err instanceof Error) {
|
|
65
|
+
const status = err.status;
|
|
66
|
+
return status === void 0 ? err.message : `${err.message} (${status})`;
|
|
67
|
+
}
|
|
68
|
+
return String(err);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/runner/retry.ts
|
|
72
|
+
var RETRY_WINDOW_MS = 12e4;
|
|
73
|
+
var FIRST_BACKOFF_MS = 1e3;
|
|
74
|
+
var MAX_BACKOFF_MS = 15e3;
|
|
75
|
+
function isRetryable(err) {
|
|
76
|
+
if (err instanceof WorkCommandError) return err.retryable;
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
async function retrySend(send, deps = {}) {
|
|
80
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
81
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
82
|
+
const windowMs = deps.windowMs ?? RETRY_WINDOW_MS;
|
|
83
|
+
const startedMs = now().getTime();
|
|
84
|
+
let attempts = 0;
|
|
85
|
+
let backoffMs = FIRST_BACKOFF_MS;
|
|
86
|
+
for (; ; ) {
|
|
87
|
+
attempts++;
|
|
88
|
+
try {
|
|
89
|
+
return { ok: true, value: await send(), attempts };
|
|
90
|
+
} catch (err) {
|
|
91
|
+
const reason = describeCallFailure(err);
|
|
92
|
+
const code = err instanceof WorkCommandError ? err.code : void 0;
|
|
93
|
+
const status = err instanceof WorkCommandError ? err.status : void 0;
|
|
94
|
+
const message = err instanceof WorkCommandError ? err.message : void 0;
|
|
95
|
+
if (!isRetryable(err)) return { ok: false, attempts, reason, retryable: false, code, status, message };
|
|
96
|
+
const elapsedMs = now().getTime() - startedMs;
|
|
97
|
+
if (elapsedMs + backoffMs >= windowMs) return { ok: false, attempts, reason, retryable: true, code, status, message };
|
|
98
|
+
await sleep(backoffMs);
|
|
99
|
+
backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/runner/build-tokens.ts
|
|
105
|
+
var ISSUE_RETRY_WINDOW_MS = 3e4;
|
|
106
|
+
var REVOKE_TIMEOUT_MS = 15e3;
|
|
107
|
+
function isDue(expiresAtMs, nowMs) {
|
|
108
|
+
return nowMs >= expiresAtMs - 10 * 6e4;
|
|
109
|
+
}
|
|
110
|
+
function classifyRefusal(err) {
|
|
111
|
+
if (!(err instanceof WorkCommandError)) return { kind: "retry" };
|
|
112
|
+
if (err.status === 401 || err.code === "claim_expired" || err.code === "claim_not_held") return { kind: "lease_lost" };
|
|
113
|
+
if (err.code === "github_unavailable") return { kind: "retry" };
|
|
114
|
+
if (err.code === "github_request_failed") return { kind: "failed", reason: refusalCode(err) };
|
|
115
|
+
if (err.retryable && err.code !== "repository_tokens_unavailable") return { kind: "retry" };
|
|
116
|
+
return { kind: "failed", reason: refusalCode(err) };
|
|
117
|
+
}
|
|
118
|
+
function refusalCode(err) {
|
|
119
|
+
if (!(err instanceof WorkCommandError)) return "unavailable";
|
|
120
|
+
return err.code ?? `http_${err.status ?? "unknown"}`;
|
|
121
|
+
}
|
|
122
|
+
function keyOf(kind, repository) {
|
|
123
|
+
return repository === void 0 ? kind : `${kind}:${repository}`;
|
|
124
|
+
}
|
|
125
|
+
var BuildTokens = class {
|
|
126
|
+
constructor(options) {
|
|
127
|
+
this.options = options;
|
|
128
|
+
this.held = /* @__PURE__ */ new Map();
|
|
129
|
+
this.asking = /* @__PURE__ */ new Map();
|
|
130
|
+
this.issued = [];
|
|
131
|
+
this.usedKinds = /* @__PURE__ */ new Set();
|
|
132
|
+
this.revoked = false;
|
|
133
|
+
this.controlPlaneRetry = false;
|
|
134
|
+
}
|
|
135
|
+
now() {
|
|
136
|
+
return (this.options.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
137
|
+
}
|
|
138
|
+
/** `repository` as the holder keys it: `undefined` for the primary, which is the one a request names no repository for. */
|
|
139
|
+
secondary(repository) {
|
|
140
|
+
if (repository === void 0) return void 0;
|
|
141
|
+
const primary = this.options.primary;
|
|
142
|
+
return primary !== void 0 && primary.toLowerCase() === repository.toLowerCase() ? void 0 : repository;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The token of `kind` for `repository`, or for the primary when none is
|
|
146
|
+
* named, asking the gateway for a new one when the build holds none or its
|
|
147
|
+
* token is within ten minutes of expiry.
|
|
148
|
+
*/
|
|
149
|
+
token(kind, repository) {
|
|
150
|
+
const other = this.secondary(repository);
|
|
151
|
+
const key = keyOf(kind, other);
|
|
152
|
+
const held = this.held.get(key);
|
|
153
|
+
if (held !== void 0 && !isDue(held.expiresAtMs, this.now().getTime())) return Promise.resolve(held.token);
|
|
154
|
+
let asking = this.asking.get(key);
|
|
155
|
+
if (asking === void 0) {
|
|
156
|
+
asking = this.issueOrKeep(kind, other).finally(() => this.asking.delete(key));
|
|
157
|
+
this.asking.set(key, asking);
|
|
158
|
+
}
|
|
159
|
+
return asking;
|
|
160
|
+
}
|
|
161
|
+
/** A due token that has not yet expired is kept when the gateway cannot issue a new one. */
|
|
162
|
+
async issueOrKeep(kind, repository) {
|
|
163
|
+
try {
|
|
164
|
+
return await this.issue(kind, repository);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const held = this.held.get(keyOf(kind, repository));
|
|
167
|
+
if (held !== void 0 && this.now().getTime() < held.expiresAtMs && classifyRefusal(err).kind === "retry") return held.token;
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Asks the gateway for a token with `body`, retrying a failure about the moment. */
|
|
172
|
+
ask(body) {
|
|
173
|
+
const { config, item, fetchImpl } = this.options;
|
|
174
|
+
return retrySend(
|
|
175
|
+
async () => {
|
|
176
|
+
try {
|
|
177
|
+
return await postJson(
|
|
178
|
+
config.url,
|
|
179
|
+
`/v1/work-items/${item}/claim/tokens`,
|
|
180
|
+
config.apiKey,
|
|
181
|
+
config.appId,
|
|
182
|
+
body,
|
|
183
|
+
fetchImpl,
|
|
184
|
+
void 0,
|
|
185
|
+
await signerFor(config.configPath)
|
|
186
|
+
);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (err instanceof WorkCommandError && (err.code === "repository_tokens_unavailable" || err.code === "github_request_failed")) {
|
|
189
|
+
throw new WorkCommandError(err.message, false, err.status, err.code);
|
|
190
|
+
}
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
{ now: this.options.now, sleep: this.options.sleep, windowMs: ISSUE_RETRY_WINDOW_MS }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A read-only token for the repository that governs the item's, which the
|
|
199
|
+
* runner uses on the host to fetch its context. It is revoked with the
|
|
200
|
+
* others when the attempt ends. It is not held or renewed: nothing uses it
|
|
201
|
+
* after the fetch. A refusal is thrown with the gateway's code. A lost
|
|
202
|
+
* lease is kept for `refusal()`, and a failure about the moment for
|
|
203
|
+
* `controlPlaneUnavailable()`, as the read token's are.
|
|
204
|
+
*/
|
|
205
|
+
async controlPlane() {
|
|
206
|
+
const asked = await this.ask({ access: "read", target: "control_plane" });
|
|
207
|
+
if (!asked.ok) {
|
|
208
|
+
const err = new WorkCommandError(asked.message ?? asked.reason, asked.retryable, asked.status, asked.code);
|
|
209
|
+
const refusal = classifyRefusal(err);
|
|
210
|
+
if (refusal.kind === "lease_lost") this.failure = refusal;
|
|
211
|
+
if (refusal.kind === "retry") this.controlPlaneRetry = true;
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
const { token, expiresAt, repository } = asked.value.data;
|
|
215
|
+
if (typeof token !== "string" || token === "" || typeof expiresAt !== "string" || typeof repository !== "string" || repository === "") {
|
|
216
|
+
throw new WorkCommandError("the gateway's token answer carried no token, expiry or repository", false, void 0, "malformed_token_response");
|
|
217
|
+
}
|
|
218
|
+
if (this.revoked) {
|
|
219
|
+
await this.revoke([token]);
|
|
220
|
+
throw new WorkCommandError("the build's tokens were already revoked", false, void 0, "tokens_revoked");
|
|
221
|
+
}
|
|
222
|
+
this.issued.push(token);
|
|
223
|
+
return { token, expiresAt, repository };
|
|
224
|
+
}
|
|
225
|
+
async issue(kind, repository) {
|
|
226
|
+
const asked = await this.ask({ access: kind, ...repository === void 0 ? {} : { repository } });
|
|
227
|
+
let err;
|
|
228
|
+
if (!asked.ok) {
|
|
229
|
+
err = new WorkCommandError(asked.message ?? asked.reason, asked.retryable, asked.status, asked.code);
|
|
230
|
+
} else {
|
|
231
|
+
const { token, expiresAt } = asked.value.data;
|
|
232
|
+
const expiresAtMs = typeof expiresAt === "string" ? Date.parse(expiresAt) : Number.NaN;
|
|
233
|
+
if (typeof token === "string" && token !== "" && Number.isFinite(expiresAtMs)) {
|
|
234
|
+
if (this.revoked) {
|
|
235
|
+
await this.revoke([token]);
|
|
236
|
+
throw new WorkCommandError("the build's tokens were already revoked", false, void 0, "tokens_revoked");
|
|
237
|
+
}
|
|
238
|
+
this.held.set(keyOf(kind, repository), { token, expiresAtMs });
|
|
239
|
+
this.issued.push(token);
|
|
240
|
+
if (kind === "read" && repository === void 0) this.readSink?.(token);
|
|
241
|
+
return token;
|
|
242
|
+
}
|
|
243
|
+
err = new WorkCommandError("the gateway's token answer carried no token or no expiry", false, void 0, "malformed_token_response");
|
|
244
|
+
}
|
|
245
|
+
const refusal = classifyRefusal(err);
|
|
246
|
+
if (refusal.kind !== "retry") this.failure = refusal;
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
/** Asks for a new read token when the build holds one within ten minutes of expiry. A refusal is kept for `refusal()`, not thrown. */
|
|
250
|
+
async renewReadIfDue() {
|
|
251
|
+
const held = this.held.get("read");
|
|
252
|
+
if (held === void 0 || !isDue(held.expiresAtMs, this.now().getTime())) return;
|
|
253
|
+
await this.token("read").catch(() => void 0);
|
|
254
|
+
}
|
|
255
|
+
/** Calls `sink` with the read token now, and again with each one that replaces it. */
|
|
256
|
+
onRead(sink) {
|
|
257
|
+
this.readSink = sink;
|
|
258
|
+
const held = this.held.get("read");
|
|
259
|
+
if (held !== void 0) sink(held.token);
|
|
260
|
+
}
|
|
261
|
+
markUsed(kind) {
|
|
262
|
+
if (this.usedKinds.has(kind)) return;
|
|
263
|
+
this.usedKinds.add(kind);
|
|
264
|
+
this.options.onUsed?.(this.used());
|
|
265
|
+
}
|
|
266
|
+
/** The kinds of token the build has used, read before push. */
|
|
267
|
+
used() {
|
|
268
|
+
return ["read", "push"].filter((kind) => this.usedKinds.has(kind));
|
|
269
|
+
}
|
|
270
|
+
/** The last refusal that ends the build, if a renewal or a push's token request met one. */
|
|
271
|
+
refusal() {
|
|
272
|
+
return this.failure;
|
|
273
|
+
}
|
|
274
|
+
/** Whether a control plane token request failed for a reason about the moment, which a later attempt may not meet. */
|
|
275
|
+
controlPlaneUnavailable() {
|
|
276
|
+
return this.controlPlaneRetry;
|
|
277
|
+
}
|
|
278
|
+
/** Revokes every token this holder was issued, the superseded ones too, at GitHub with the token itself. A failure is not an error: the token lapses within the hour. */
|
|
279
|
+
async revokeAll() {
|
|
280
|
+
this.revoked = true;
|
|
281
|
+
const tokens = this.issued.splice(0);
|
|
282
|
+
this.held.clear();
|
|
283
|
+
await this.revoke(tokens);
|
|
284
|
+
}
|
|
285
|
+
async revoke(tokens) {
|
|
286
|
+
const apiUrl = this.options.apiUrl ?? "https://api.github.com";
|
|
287
|
+
await Promise.allSettled(
|
|
288
|
+
tokens.map(
|
|
289
|
+
(token) => this.options.fetchImpl(`${apiUrl}/installation/token`, {
|
|
290
|
+
method: "DELETE",
|
|
291
|
+
headers: {
|
|
292
|
+
authorization: `Bearer ${token}`,
|
|
293
|
+
accept: "application/vnd.github+json",
|
|
294
|
+
"x-github-api-version": "2022-11-28",
|
|
295
|
+
"user-agent": "outerlayer-runner"
|
|
296
|
+
},
|
|
297
|
+
signal: AbortSignal.timeout(REVOKE_TIMEOUT_MS)
|
|
298
|
+
})
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
function deliverySection(dir) {
|
|
304
|
+
return `This repository's control plane is not in the checkout. Its files are in \`${dir}\`. When an instruction or skill names a path under \`.outerlayer/\` or \`.claude/\`, read it under \`${dir}\`.`;
|
|
305
|
+
}
|
|
306
|
+
function isFile(path) {
|
|
307
|
+
try {
|
|
308
|
+
return lstatSync(path).isFile();
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function filesUnder(dir, base = dir) {
|
|
314
|
+
if (!existsSync(dir) || !lstatSync(dir).isDirectory()) return [];
|
|
315
|
+
const out = [];
|
|
316
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
317
|
+
const abs = join(dir, entry.name);
|
|
318
|
+
if (entry.isDirectory()) out.push(...filesUnder(abs, base));
|
|
319
|
+
else if (entry.isFile()) out.push(abs.slice(base.length + 1));
|
|
320
|
+
}
|
|
321
|
+
return out.sort();
|
|
322
|
+
}
|
|
323
|
+
function readText(path) {
|
|
324
|
+
if (!isFile(path)) return void 0;
|
|
325
|
+
const content = readFileSync(path);
|
|
326
|
+
return content.includes(0) ? void 0 : content.toString("utf8");
|
|
327
|
+
}
|
|
328
|
+
function deliveredSettings(raw, dir) {
|
|
329
|
+
if (raw === void 0) return "{}\n";
|
|
330
|
+
const rewrite = (value) => {
|
|
331
|
+
if (typeof value === "string") return rewriteHookCommand(value, dir);
|
|
332
|
+
if (Array.isArray(value)) return value.map(rewrite);
|
|
333
|
+
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, inner]) => [key, rewrite(inner)]));
|
|
334
|
+
return value;
|
|
335
|
+
};
|
|
336
|
+
try {
|
|
337
|
+
return `${JSON.stringify(rewrite(JSON.parse(raw)), null, 2)}
|
|
338
|
+
`;
|
|
339
|
+
} catch {
|
|
340
|
+
return rewriteHookCommand(raw, dir);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function composeDelivery(input) {
|
|
344
|
+
const { tree, dir } = input;
|
|
345
|
+
const added = [];
|
|
346
|
+
const put = (rel, content) => {
|
|
347
|
+
mkdirSync(dirname(join(tree, rel)), { recursive: true });
|
|
348
|
+
writeFileSync(join(tree, rel), content);
|
|
349
|
+
added.push(rel);
|
|
350
|
+
};
|
|
351
|
+
for (const skill of MAINTAINED_SKILLS) {
|
|
352
|
+
for (const rel of filesUnder(join(tree, ".outerlayer/skills", skill))) {
|
|
353
|
+
const target = `.claude/skills/${skill}/${rel}`;
|
|
354
|
+
if (existsSync(join(tree, target))) continue;
|
|
355
|
+
mkdirSync(dirname(join(tree, target)), { recursive: true });
|
|
356
|
+
copyFileSync(join(tree, ".outerlayer/skills", skill, rel), join(tree, target));
|
|
357
|
+
added.push(target);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const rel of [...filesUnder(join(tree, ".claude/skills"), tree), ...filesUnder(join(tree, ".claude/agents"), tree)]) {
|
|
361
|
+
const text = readText(join(tree, rel));
|
|
362
|
+
if (text === void 0) continue;
|
|
363
|
+
const rewritten = rewriteControlPlanePaths(text, dir);
|
|
364
|
+
if (rewritten !== text) writeFileSync(join(tree, rel), rewritten);
|
|
365
|
+
}
|
|
366
|
+
const agents = readText(join(tree, "AGENTS.md"));
|
|
367
|
+
put("CLAUDE.md", agents === void 0 ? `${deliverySection(dir)}
|
|
368
|
+
` : `${deliverySection(dir)}
|
|
369
|
+
|
|
370
|
+
${rewriteControlPlanePaths(agents, dir)}`);
|
|
371
|
+
put("settings.json", deliveredSettings(readText(join(tree, ".claude/settings.json")), dir));
|
|
372
|
+
return added;
|
|
373
|
+
}
|
|
374
|
+
function sharedSkillNames(tree) {
|
|
375
|
+
const dir = join(tree, ".claude/skills");
|
|
376
|
+
if (!existsSync(dir)) return [];
|
|
377
|
+
return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
378
|
+
}
|
|
379
|
+
function shimScript() {
|
|
380
|
+
return SHIM_SCRIPT;
|
|
381
|
+
}
|
|
382
|
+
function commandWithShim(job, cmdline) {
|
|
383
|
+
return job.environment?.controlPlane === void 0 ? cmdline : `PATH=${SHIM_DIR}:"$PATH"; export PATH; ${cmdline}`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// src/runner/container/context.ts
|
|
387
|
+
var TokenRefused = class extends Error {
|
|
388
|
+
constructor(code, detail) {
|
|
389
|
+
super(code);
|
|
390
|
+
this.code = code;
|
|
391
|
+
this.detail = detail;
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var NoControlPlane = class extends Error {
|
|
395
|
+
};
|
|
396
|
+
var GatewayConflict = class extends Error {
|
|
397
|
+
};
|
|
398
|
+
var unavailable = (why) => ({ kind: "refused", reason: `context_unavailable: ${why}` });
|
|
399
|
+
var DESIGNATION_LOOKUP = { timeoutMs: 1e4, attempts: 2 };
|
|
400
|
+
function lookupFailureReason(failure, repository) {
|
|
401
|
+
switch (failure?.kind) {
|
|
402
|
+
case "timeout":
|
|
403
|
+
return `the gateway did not answer which repository governs ${repository} within ${failure.timeoutMs / 1e3} seconds, twice`;
|
|
404
|
+
case "status":
|
|
405
|
+
return `the gateway answered ${failure.status} when asked which repository governs ${repository}`;
|
|
406
|
+
case "network":
|
|
407
|
+
return `the request asking which repository governs ${repository} failed: ${failure.message}`;
|
|
408
|
+
default:
|
|
409
|
+
return `the gateway did not say which repository governs ${repository}`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function controlPlaneTokenFailure(err, repository) {
|
|
413
|
+
const code = refusalCode(err);
|
|
414
|
+
const detail = err instanceof WorkCommandError && err.code === void 0 && err.status === void 0 ? describeCallFailure(err) : void 0;
|
|
415
|
+
return detail === void 0 ? `context_unavailable: the gateway refused a token for the control plane named for ${repository} (${code})` : `context_unavailable: the token request for the control plane named for ${repository} failed: ${detail}`;
|
|
416
|
+
}
|
|
417
|
+
function makeReadable(tree, dir, files) {
|
|
418
|
+
chmodSync(dir, 493);
|
|
419
|
+
chmodSync(tree, 493);
|
|
420
|
+
const directories = /* @__PURE__ */ new Set();
|
|
421
|
+
for (const rel of files) {
|
|
422
|
+
for (let current = dirname(join(tree, rel)); current !== tree; current = dirname(current)) directories.add(current);
|
|
423
|
+
chmodSync(join(tree, rel), (lstatSync(join(tree, rel)).mode & 73) !== 0 ? 493 : 420);
|
|
424
|
+
}
|
|
425
|
+
for (const directory of directories) chmodSync(directory, 493);
|
|
426
|
+
}
|
|
427
|
+
async function stageGovernedContext(input) {
|
|
428
|
+
const git = input.git ?? defaultGitRunner;
|
|
429
|
+
const dir = stagedContextDir(input.buildDir);
|
|
430
|
+
const tree = join(dir, "tree");
|
|
431
|
+
const finish = (outcome) => {
|
|
432
|
+
removeStagedContext(input.buildDir);
|
|
433
|
+
return outcome;
|
|
434
|
+
};
|
|
435
|
+
try {
|
|
436
|
+
mkdirSync(tree, { recursive: true });
|
|
437
|
+
for (const args of [
|
|
438
|
+
["init", "-q", "--initial-branch=main", tree],
|
|
439
|
+
["-C", tree, "remote", "add", "origin", `https://github.com/${input.repository}.git`]
|
|
440
|
+
]) {
|
|
441
|
+
const done = git(args, { cwd: dir, timeoutMs: 1e4 });
|
|
442
|
+
if (!done.ok) return finish(unavailable(`the staging directory could not be prepared: ${done.stderr.trim().split("\n")[0] ?? ""}`));
|
|
443
|
+
}
|
|
444
|
+
const { url, apiKey, appId, configPath } = input.config;
|
|
445
|
+
let tokenRepository;
|
|
446
|
+
const result = await materializeContext({
|
|
447
|
+
cwd: tree,
|
|
448
|
+
sessionId: `build-${input.buildId}`,
|
|
449
|
+
home: contextHomeDir(configPath),
|
|
450
|
+
cloud: { url, apiKey, appId },
|
|
451
|
+
fetchImpl: signingFetchFor(input.fetchImpl, configPath, apiKey),
|
|
452
|
+
designationLookup: DESIGNATION_LOOKUP,
|
|
453
|
+
...input.source === void 0 ? {} : { pinnedSource: input.source },
|
|
454
|
+
git,
|
|
455
|
+
now: input.now,
|
|
456
|
+
resolveControlPlaneUrl: (repository) => input.controlPlaneUrl(repository),
|
|
457
|
+
controlPlaneAccess: input.controlPlaneToken === void 0 ? void 0 : async (candidates) => {
|
|
458
|
+
const access = await controlPlaneAccess(input.controlPlaneToken, candidates, input.repository, input.log, input.tag);
|
|
459
|
+
tokenRepository = access?.repository;
|
|
460
|
+
return access;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
switch (result.status) {
|
|
464
|
+
case "materialized": {
|
|
465
|
+
const manifest = readContextManifest(join(tree, ".git"));
|
|
466
|
+
const source = readSourceCache(join(tree, ".git"));
|
|
467
|
+
if (result.sha === null || manifest === void 0 || source?.status !== "governed" || source.repository === void 0 || source.ref === void 0) {
|
|
468
|
+
return finish(unavailable(`the context of ${input.repository} was materialized without a record of it`));
|
|
469
|
+
}
|
|
470
|
+
const composed = composeDelivery({ tree, dir: deliveryDir(source.repository, result.sha) });
|
|
471
|
+
writeFileSync(
|
|
472
|
+
join(dir, "staged.json"),
|
|
473
|
+
JSON.stringify({ schemaVersion: 1, sha: result.sha, repository: source.repository, ref: source.ref, files: manifest.files }) + "\n"
|
|
474
|
+
);
|
|
475
|
+
makeReadable(tree, dir, [.../* @__PURE__ */ new Set([...manifest.files, ...composed])]);
|
|
476
|
+
chmodSync(join(dir, "staged.json"), 420);
|
|
477
|
+
return { kind: "staged", dir, sha: result.sha, repository: source.repository, ref: source.ref, files: manifest.files };
|
|
478
|
+
}
|
|
479
|
+
case "self":
|
|
480
|
+
if (result.designation === "answered") return finish({ kind: "none" });
|
|
481
|
+
if (result.authRejected === true) {
|
|
482
|
+
return finish(unavailable(`the gateway rejected the runner's API key (401/403) when asked which repository governs ${input.repository}`));
|
|
483
|
+
}
|
|
484
|
+
if (result.message !== void 0) return finish(unavailable(result.message));
|
|
485
|
+
return finish(unavailable(lookupFailureReason(result.lookupFailure, input.repository)));
|
|
486
|
+
case "unreachable":
|
|
487
|
+
case "no-access": {
|
|
488
|
+
const plane = isStagingWriteFault(result.message) ? void 0 : readDegraded(join(tree, ".git"))?.repository;
|
|
489
|
+
if (plane !== void 0) {
|
|
490
|
+
const credential = tokenRepository !== void 0 && tokenRepository.toLowerCase() === plane.toLowerCase() ? "the token the gateway issued" : "the host's git login";
|
|
491
|
+
return finish(unavailable(`${credential} cannot read ${plane}, the control plane named for ${input.repository}`));
|
|
492
|
+
}
|
|
493
|
+
return finish(unavailable(result.message ?? `the context of ${input.repository} could not be materialized (${result.status})`));
|
|
494
|
+
}
|
|
495
|
+
default:
|
|
496
|
+
return finish(unavailable(result.message ?? `the context of ${input.repository} could not be materialized (${result.status})`));
|
|
497
|
+
}
|
|
498
|
+
} catch (err) {
|
|
499
|
+
if (err instanceof NoControlPlane) return finish({ kind: "none" });
|
|
500
|
+
if (err instanceof GatewayConflict) return finish({ kind: "refused", reason: `context_conflict: ${err.message}` });
|
|
501
|
+
if (err instanceof TokenRefused) {
|
|
502
|
+
return finish(
|
|
503
|
+
unavailable(
|
|
504
|
+
err.detail === void 0 ? `the gateway refused a token for the control plane named for ${input.repository} (${err.code})` : `the token request for the control plane named for ${input.repository} failed: ${err.detail}`
|
|
505
|
+
)
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return finish(unavailable(`the context of ${input.repository} could not be staged: ${err instanceof Error ? err.message : String(err)}`));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async function controlPlaneAccess(ask, candidates, repository, log, tag) {
|
|
512
|
+
let answer;
|
|
513
|
+
try {
|
|
514
|
+
answer = await ask();
|
|
515
|
+
} catch (err) {
|
|
516
|
+
const code = refusalCode(err);
|
|
517
|
+
if (code === "repository_tokens_unavailable") return void 0;
|
|
518
|
+
if (code === "no_control_plane") throw new NoControlPlane(code);
|
|
519
|
+
if (code === "context_conflict") throw new GatewayConflict(err instanceof Error ? err.message : code);
|
|
520
|
+
throw new TokenRefused(code, err instanceof WorkCommandError && err.code === void 0 && err.status === void 0 ? describeCallFailure(err) : void 0);
|
|
521
|
+
}
|
|
522
|
+
const named = candidates.some((candidate) => candidate.repository.toLowerCase() === answer.repository.toLowerCase());
|
|
523
|
+
if (!named) {
|
|
524
|
+
log?.line(
|
|
525
|
+
`${tag === void 0 ? "" : `${tag} `}the gateway issued a control plane token for ${answer.repository}, which is none of the repositories named for ${repository} (${candidates.map((candidate) => candidate.repository).join(", ")}) \xB7 fetching with the host's git login`
|
|
526
|
+
);
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
return answer;
|
|
530
|
+
}
|
|
531
|
+
function removeStagedContext(buildDir) {
|
|
532
|
+
rmSync(stagedContextDir(buildDir), { recursive: true, force: true });
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/runner/governing-source.ts
|
|
536
|
+
var unavailable2 = (why) => ({ ok: false, reason: `context_unavailable: ${why}` });
|
|
537
|
+
function pinnedSourceOf(source) {
|
|
538
|
+
return source.kind === "self" ? null : { repository: source.repository, ref: source.ref, sha: source.sha };
|
|
539
|
+
}
|
|
540
|
+
async function resolveGoverningSource(input) {
|
|
541
|
+
const { url, apiKey, appId, configPath } = input.config;
|
|
542
|
+
const lookup = await lookupGoverningSource({ url, apiKey, appId }, input.repository, signingFetchFor(input.fetchImpl, configPath, apiKey), DESIGNATION_LOOKUP);
|
|
543
|
+
switch (lookup.kind) {
|
|
544
|
+
case "rejected":
|
|
545
|
+
return unavailable2(`the gateway rejected the runner's API key (401/403) when asked which repository governs ${input.repository}`);
|
|
546
|
+
case "failed":
|
|
547
|
+
return unavailable2(lookupFailureReason(lookup.failure, input.repository));
|
|
548
|
+
case "answered":
|
|
549
|
+
if (lookup.source === null) return { ok: true, source: { kind: "self" } };
|
|
550
|
+
if (lookup.source === void 0) {
|
|
551
|
+
return unavailable2("the OuterLayer this machine talks to is too old to name the control plane \u2014 ask whoever runs it to upgrade");
|
|
552
|
+
}
|
|
553
|
+
return { ok: true, source: { kind: "governed", ...lookup.source } };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function imageTargetFor(input) {
|
|
557
|
+
if (input.config === void 0) return { ok: true, repository: input.repository };
|
|
558
|
+
const resolved = await resolveGoverningSource({ config: input.config, repository: input.repository, fetchImpl: input.fetchImpl });
|
|
559
|
+
if (!resolved.ok) return resolved;
|
|
560
|
+
if (resolved.source.kind === "self") return { ok: true, repository: input.repository };
|
|
561
|
+
return { ok: true, repository: resolved.source.repository, pinned: { commit: resolved.source.sha, ref: resolved.source.ref } };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export { BuildTokens, classifyRefusal, commandWithShim, controlPlaneTokenFailure, createRunnerLog, describeCallFailure, formatClock, formatDuration, imageTargetFor, pinnedSourceOf, refusalCode, relativeToRunner, removeStagedContext, resolveGoverningSource, retrySend, sharedSkillNames, shimScript, shortClaim, stageGovernedContext };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { runnerDir, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-
|
|
3
|
+
import { runnerDir, RUNNER_DIR_MODE, RUNNER_FILE_MODE } from './chunk-N2PXGQRL.js';
|
|
4
4
|
import { readlinkSync, realpathSync, mkdirSync, unlinkSync, symlinkSync, renameSync, readFileSync, writeFileSync, rmSync, existsSync, readdirSync } from 'fs';
|
|
5
5
|
import { join, basename, sep, resolve } from 'path';
|
|
6
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
+
import { cliInvocation } from './chunk-CRB3POP4.js';
|
|
3
4
|
|
|
4
5
|
createRequire(import.meta.url);
|
|
5
6
|
|
|
@@ -49,6 +50,6 @@ function warnDeprecated(message) {
|
|
|
49
50
|
process.stderr.write(`${C.yellow}!${C.reset} ${message}
|
|
50
51
|
`);
|
|
51
52
|
}
|
|
52
|
-
var API_KEY_FLAG_DEPRECATION =
|
|
53
|
+
var API_KEY_FLAG_DEPRECATION = `--api-key is deprecated: a key on the command line lands in shell history and process lists. Run \`${cliInvocation()} login\` once, or set OUTERLAYER_API_KEY.`;
|
|
53
54
|
|
|
54
55
|
export { API_KEY_FLAG_DEPRECATION, C, NO_COLOR_FLAG, stripNoColor, warnDeprecated };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { autoConnect } from './chunk-
|
|
3
|
+
import { autoConnect } from './chunk-WBT5X3NH.js';
|
|
4
4
|
import { detectActiveSession } from './chunk-IP7BIBXJ.js';
|
|
5
|
-
import { resolveRepoIdentity } from './chunk-
|
|
6
|
-
import { C } from './chunk-
|
|
7
|
-
import { parseWorkItemEnv } from './chunk-
|
|
5
|
+
import { resolveRepoIdentity } from './chunk-BMYGAGBJ.js';
|
|
6
|
+
import { C } from './chunk-ANPNT6PM.js';
|
|
7
|
+
import { parseWorkItemEnv } from './chunk-T2Q7ISPE.js';
|
|
8
8
|
import { parseTrackerNumber, trackerNumberError } from './chunk-WZBHBH3J.js';
|
|
9
|
-
import { defaultFetch } from './chunk-
|
|
10
|
-
import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-
|
|
9
|
+
import { defaultFetch } from './chunk-ZRL6HKBY.js';
|
|
10
|
+
import { unauthorizedMessage, resolveGatewayCredentials } from './chunk-52NC2FIU.js';
|
|
11
11
|
import { homeFromEnv } from './chunk-VNVZDWO3.js';
|
|
12
12
|
|
|
13
13
|
createRequire(import.meta.url);
|