@mutmutco/cli 4.5.226 → 4.5.228
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/dist/main.cjs +495 -231
- package/dist/runbook-sweep.cjs +1 -0
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -982,12 +982,14 @@ function roleFromToken(token) {
|
|
|
982
982
|
function readCache(path2, apiUrl, now, githubTokenFingerprint) {
|
|
983
983
|
try {
|
|
984
984
|
const session = JSON.parse((0, import_node_fs3.readFileSync)(path2, "utf8"));
|
|
985
|
-
if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return null;
|
|
986
|
-
if (session.githubTokenFingerprint !== githubTokenFingerprint)
|
|
987
|
-
|
|
988
|
-
|
|
985
|
+
if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return { session: null, stale: false };
|
|
986
|
+
if (session.githubTokenFingerprint !== githubTokenFingerprint) {
|
|
987
|
+
return { session: null, stale: true, staleLogin: typeof session.login === "string" ? session.login : void 0 };
|
|
988
|
+
}
|
|
989
|
+
if (new Date(session.expiresAt).getTime() <= now.getTime() + REFRESH_WINDOW_MS) return { session: null, stale: false };
|
|
990
|
+
return { session: { ...session, role: roleFromToken(session.token) }, stale: false };
|
|
989
991
|
} catch {
|
|
990
|
-
return null;
|
|
992
|
+
return { session: null, stale: false };
|
|
991
993
|
}
|
|
992
994
|
}
|
|
993
995
|
function writeCache(path2, session) {
|
|
@@ -1004,53 +1006,115 @@ function writeCache(path2, session) {
|
|
|
1004
1006
|
} catch {
|
|
1005
1007
|
}
|
|
1006
1008
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
+
function lastHubAuthFailure() {
|
|
1010
|
+
return lastFailure;
|
|
1011
|
+
}
|
|
1012
|
+
function hubAuthFailureMessage(input) {
|
|
1013
|
+
const r = input.refusal;
|
|
1014
|
+
const said = r ? [
|
|
1015
|
+
r.error ? `Hub error: ${r.error}` : `Hub error: HTTP ${r.status ?? "unknown"}`,
|
|
1016
|
+
r.reason ? `reason: ${r.reason}` : "",
|
|
1017
|
+
r.hint ? `hint: ${r.hint}` : ""
|
|
1018
|
+
].filter(Boolean).join(" ") : "";
|
|
1019
|
+
if (input.kind === "stale-session") {
|
|
1020
|
+
return `cached Hub session is stale: it was minted from a different GitHub token${input.staleLogin ? ` (cached login ${input.staleLogin})` : ""}, and the automatic re-mint was refused${said ? ` \u2014 ${said}` : ""}. Re-authenticate so ONE token source feeds both the cache and the mint (\`gh auth login\` / \`gh auth switch\`, or unset \`MMI_GH_PAT\` so the shell falls back to the gh keyring), then retry; \`mmi-cli whoami\` confirms the identity.`;
|
|
1021
|
+
}
|
|
1022
|
+
if (input.kind === "refused") {
|
|
1023
|
+
return `Hub refused this GitHub token for a Hub session${said ? ` \u2014 ${said}` : ""} (${MEMBERSHIP_FIX}).`;
|
|
1024
|
+
}
|
|
1025
|
+
return `Hub session exchange failed: ${(input.transient ?? said) || "no answer from the Hub"} \u2014 the Hub is unreachable or cold-started; retry, and \`mmi-cli doctor\` reports Hub reachability.`;
|
|
1026
|
+
}
|
|
1027
|
+
async function hubAuthSessionOutcome(deps) {
|
|
1028
|
+
if (!deps.baseUrl) return {};
|
|
1009
1029
|
const apiUrl = normalizeBaseUrl(deps.baseUrl);
|
|
1010
1030
|
const envToken = process.env.MMI_HUB_TOKEN;
|
|
1011
1031
|
if (envToken) {
|
|
1012
|
-
|
|
1032
|
+
lastFailure = void 0;
|
|
1033
|
+
return { session: { ...{ token: envToken }, expiresAt: new Date(Date.now() + 30 * 60 * 1e3).toISOString(), apiUrl, githubTokenFingerprint: "env-override" } };
|
|
1013
1034
|
}
|
|
1014
1035
|
const now = deps.now?.() ?? /* @__PURE__ */ new Date();
|
|
1015
1036
|
const cachePath = deps.cachePath ?? defaultHubSessionCachePath();
|
|
1016
1037
|
const ghToken = await deps.githubToken();
|
|
1017
|
-
if (!ghToken)
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
try {
|
|
1022
|
-
const res = await fetchWithRetry(
|
|
1023
|
-
deps.fetch ?? fetch,
|
|
1024
|
-
`${apiUrl}/auth/session`,
|
|
1025
|
-
{ method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
|
|
1026
|
-
{ attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
|
|
1027
|
-
);
|
|
1028
|
-
if (!res.ok) return void 0;
|
|
1029
|
-
const body = await res.json();
|
|
1030
|
-
if (!body.token || !body.expiresAt) return void 0;
|
|
1031
|
-
const session = {
|
|
1032
|
-
token: body.token,
|
|
1033
|
-
expiresAt: body.expiresAt,
|
|
1034
|
-
login: typeof body.login === "string" ? body.login : void 0,
|
|
1035
|
-
// #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
|
|
1036
|
-
// read and the network read cannot disagree about what counts as an assertion.
|
|
1037
|
-
role: roleFromToken(body.token),
|
|
1038
|
-
apiUrl,
|
|
1039
|
-
githubTokenFingerprint
|
|
1038
|
+
if (!ghToken) {
|
|
1039
|
+
lastFailure = {
|
|
1040
|
+
kind: "unreachable",
|
|
1041
|
+
message: "no GitHub token available for a Hub session \u2014 `gh auth login` (or set MMI_GH_PAT/GH_TOKEN), then retry."
|
|
1040
1042
|
};
|
|
1043
|
+
return { failure: lastFailure };
|
|
1044
|
+
}
|
|
1045
|
+
const githubTokenFingerprint = tokenFingerprint(ghToken);
|
|
1046
|
+
const cache = readCache(cachePath, apiUrl, now, githubTokenFingerprint);
|
|
1047
|
+
if (cache.session) {
|
|
1048
|
+
lastFailure = void 0;
|
|
1049
|
+
return { session: cache.session };
|
|
1050
|
+
}
|
|
1051
|
+
return mintSession(deps, cachePath, apiUrl, ghToken, githubTokenFingerprint, cache);
|
|
1052
|
+
}
|
|
1053
|
+
async function mintSession(deps, cachePath, apiUrl, ghToken, githubTokenFingerprint, cache) {
|
|
1054
|
+
const attempts = cache.stale ? 2 : 1;
|
|
1055
|
+
let refusal;
|
|
1056
|
+
let transient;
|
|
1057
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1041
1058
|
try {
|
|
1042
|
-
|
|
1043
|
-
|
|
1059
|
+
const res = await fetchWithRetry(
|
|
1060
|
+
deps.fetch ?? fetch,
|
|
1061
|
+
`${apiUrl}/auth/session`,
|
|
1062
|
+
{ method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
|
|
1063
|
+
{ attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
|
|
1064
|
+
);
|
|
1065
|
+
if (!res.ok) {
|
|
1066
|
+
const body2 = await res.json().catch(() => ({}));
|
|
1067
|
+
refusal = {
|
|
1068
|
+
status: res.status,
|
|
1069
|
+
error: typeof body2.error === "string" ? body2.error : void 0,
|
|
1070
|
+
reason: typeof body2.reason === "string" ? body2.reason : void 0,
|
|
1071
|
+
hint: typeof body2.hint === "string" ? body2.hint : void 0
|
|
1072
|
+
};
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const body = await res.json();
|
|
1076
|
+
if (!body.token || !body.expiresAt) {
|
|
1077
|
+
refusal = { status: res.status, error: "the Hub returned no session token" };
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const session = {
|
|
1081
|
+
token: body.token,
|
|
1082
|
+
expiresAt: body.expiresAt,
|
|
1083
|
+
login: typeof body.login === "string" ? body.login : void 0,
|
|
1084
|
+
// #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
|
|
1085
|
+
// read and the network read cannot disagree about what counts as an assertion.
|
|
1086
|
+
role: roleFromToken(body.token),
|
|
1087
|
+
apiUrl,
|
|
1088
|
+
githubTokenFingerprint
|
|
1089
|
+
};
|
|
1090
|
+
try {
|
|
1091
|
+
writeCache(cachePath, session);
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
lastFailure = void 0;
|
|
1095
|
+
return { session };
|
|
1096
|
+
} catch (e) {
|
|
1097
|
+
transient = e?.message || "network error";
|
|
1044
1098
|
}
|
|
1045
|
-
return session;
|
|
1046
|
-
} catch {
|
|
1047
|
-
return void 0;
|
|
1048
1099
|
}
|
|
1100
|
+
const kind = cache.stale ? "stale-session" : refusal ? "refused" : "unreachable";
|
|
1101
|
+
const failure = {
|
|
1102
|
+
kind,
|
|
1103
|
+
message: hubAuthFailureMessage({ kind, staleLogin: cache.staleLogin, refusal, transient }),
|
|
1104
|
+
...refusal?.reason ? { reason: refusal.reason } : {},
|
|
1105
|
+
...refusal?.hint ? { hint: refusal.hint } : {},
|
|
1106
|
+
...kind === "stale-session" ? { exitCode: EXIT_HUB_SESSION_STALE } : kind === "refused" ? { exitCode: EXIT_HUB_SESSION_REFUSED } : {}
|
|
1107
|
+
};
|
|
1108
|
+
lastFailure = failure;
|
|
1109
|
+
return { failure };
|
|
1110
|
+
}
|
|
1111
|
+
async function hubAuthSession(deps) {
|
|
1112
|
+
return (await hubAuthSessionOutcome(deps)).session;
|
|
1049
1113
|
}
|
|
1050
1114
|
async function hubAuthToken(deps) {
|
|
1051
1115
|
return (await hubAuthSession(deps))?.token;
|
|
1052
1116
|
}
|
|
1053
|
-
var import_node_crypto, import_node_fs3, import_node_path3, import_node_os2, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS;
|
|
1117
|
+
var import_node_crypto, import_node_fs3, import_node_path3, import_node_os2, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS, EXIT_HUB_SESSION_STALE, EXIT_HUB_SESSION_REFUSED, lastFailure, MEMBERSHIP_FIX;
|
|
1054
1118
|
var init_hub_auth = __esm({
|
|
1055
1119
|
"src/hub-auth.ts"() {
|
|
1056
1120
|
"use strict";
|
|
@@ -1063,6 +1127,9 @@ var init_hub_auth = __esm({
|
|
|
1063
1127
|
REFRESH_WINDOW_MS = 10 * 60 * 1e3;
|
|
1064
1128
|
EXCHANGE_TIMEOUT_MS = 8e3;
|
|
1065
1129
|
EXCHANGE_ATTEMPTS = 2;
|
|
1130
|
+
EXIT_HUB_SESSION_STALE = 76;
|
|
1131
|
+
EXIT_HUB_SESSION_REFUSED = 77;
|
|
1132
|
+
MEMBERSHIP_FIX = "the token needs Organization \u2192 Members: read (`read:org`) \u2014 use a classic PAT or an App carrying that permission, or `gh auth switch` to a login that has it; then `mmi-cli whoami` confirms the identity";
|
|
1066
1133
|
}
|
|
1067
1134
|
});
|
|
1068
1135
|
|
|
@@ -9359,12 +9426,22 @@ var isWin = process.platform === "win32";
|
|
|
9359
9426
|
var MMI_PLUGIN_ID = "mmi@mutmutco";
|
|
9360
9427
|
var LEGACY_MMI_MARKETPLACE = "mmi";
|
|
9361
9428
|
var CODEX_MARKETPLACE = "mutmutco";
|
|
9362
|
-
function
|
|
9429
|
+
function insideOpenBotAgentWorkspace(cwd, env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9430
|
+
let dir = cwd;
|
|
9431
|
+
try {
|
|
9432
|
+
dir ??= process.cwd();
|
|
9433
|
+
} catch {
|
|
9434
|
+
return false;
|
|
9435
|
+
}
|
|
9436
|
+
const rel = (0, import_node_path4.relative)(openbotAgentsRoot(env, home), dir);
|
|
9437
|
+
return !(0, import_node_path4.isAbsolute)(rel) && (rel.split(/[\\/]/)[0] ?? "").toLowerCase().startsWith("agent-");
|
|
9438
|
+
}
|
|
9439
|
+
function detectSurface(env, cwd) {
|
|
9363
9440
|
const has = (k) => Boolean(env[k]?.trim());
|
|
9441
|
+
if (env.MMI_AGENT_SURFACE === "openbot" || insideOpenBotAgentWorkspace(cwd, env)) return "openbot";
|
|
9364
9442
|
if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_THREAD_ID") || has("CODEX_MANAGED_BY_NPM") || has("CODEX_MANAGED_PACKAGE_ROOT") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
|
|
9365
9443
|
return "codex";
|
|
9366
9444
|
}
|
|
9367
|
-
if (env.MMI_AGENT_SURFACE === "hermes" || has("HERMES_SESSION_ID") || has("HERMES_PLUGIN_ROOT")) return "hermes";
|
|
9368
9445
|
if (env.MMI_AGENT_SURFACE === "jervcode" || has("PI_SESSION_ID")) {
|
|
9369
9446
|
return "jervcode";
|
|
9370
9447
|
}
|
|
@@ -9384,8 +9461,8 @@ function surfaceToken(surface) {
|
|
|
9384
9461
|
return "codex";
|
|
9385
9462
|
case "jervcode":
|
|
9386
9463
|
return "jervcode";
|
|
9387
|
-
case "
|
|
9388
|
-
return "
|
|
9464
|
+
case "openbot":
|
|
9465
|
+
return "openbot";
|
|
9389
9466
|
case "opencode":
|
|
9390
9467
|
return "opencode";
|
|
9391
9468
|
case "shell":
|
|
@@ -9403,8 +9480,8 @@ function reloadAction(surface) {
|
|
|
9403
9480
|
return "restart OpenCode";
|
|
9404
9481
|
case "jervcode":
|
|
9405
9482
|
return "restart JervCode (the package set is read at launch)";
|
|
9406
|
-
case "
|
|
9407
|
-
return "start a fresh
|
|
9483
|
+
case "openbot":
|
|
9484
|
+
return "start a fresh OpenBot agent run";
|
|
9408
9485
|
case "claude-cli":
|
|
9409
9486
|
case "shell":
|
|
9410
9487
|
default:
|
|
@@ -9444,7 +9521,7 @@ function nonClaudeSurfaceHealMessage(surface) {
|
|
|
9444
9521
|
if (surface === "codex") {
|
|
9445
9522
|
return "Codex ships an MMI plugin (#3563). Install or repair it with:\n codex plugin marketplace add mutmutco/MMI-Hub && codex plugin add mmi@mutmutco\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
9446
9523
|
}
|
|
9447
|
-
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, JervCode, and
|
|
9524
|
+
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, JervCode, and OpenBot only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
9448
9525
|
}
|
|
9449
9526
|
function healStepAborts(step, ok) {
|
|
9450
9527
|
return !ok && step.gated;
|
|
@@ -9487,14 +9564,20 @@ function runHostBin(bin, args, opts) {
|
|
|
9487
9564
|
const argv = isWin ? ["/c", bin, ...args] : args;
|
|
9488
9565
|
return step ? execFileHard(file, argv, { ...shared, step }) : execFileP(file, argv, shared);
|
|
9489
9566
|
}
|
|
9490
|
-
function
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9567
|
+
function openbotAgentsRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9568
|
+
return env.MMI_UPDATER_OPENBOT_AGENTS_DIR?.trim() || (0, import_node_path4.join)(home, "OpenBot", "Agents");
|
|
9569
|
+
}
|
|
9570
|
+
function openbotAgentWorkspaces(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9571
|
+
const root = openbotAgentsRoot(env, home);
|
|
9572
|
+
try {
|
|
9573
|
+
return (0, import_node_fs7.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("agent-")).map((entry) => (0, import_node_path4.join)(root, entry.name)).sort();
|
|
9574
|
+
} catch {
|
|
9575
|
+
return [];
|
|
9576
|
+
}
|
|
9494
9577
|
}
|
|
9495
9578
|
function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9496
9579
|
if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path4.join)(home, ".codex");
|
|
9497
|
-
if (surface === "
|
|
9580
|
+
if (surface === "openbot") return openbotAgentsRoot(env, home);
|
|
9498
9581
|
if (surface === "jervcode") {
|
|
9499
9582
|
return env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path4.join)(home, ".jerv", "agent");
|
|
9500
9583
|
}
|
|
@@ -9518,7 +9601,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
|
9518
9601
|
(0, import_node_path4.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
|
|
9519
9602
|
];
|
|
9520
9603
|
}
|
|
9521
|
-
if (surface === "
|
|
9604
|
+
if (surface === "openbot") return [];
|
|
9522
9605
|
if (surface === "jervcode") return [];
|
|
9523
9606
|
return [(0, import_node_path4.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
9524
9607
|
}
|
|
@@ -9628,13 +9711,26 @@ function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os4.homed
|
|
|
9628
9711
|
function piDoctorEnv(agentDir) {
|
|
9629
9712
|
return { ...process.env, PI_CODING_AGENT_DIR: agentDir };
|
|
9630
9713
|
}
|
|
9631
|
-
function
|
|
9632
|
-
|
|
9714
|
+
function openbotManagedRecord(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9715
|
+
for (const workspace of openbotAgentWorkspaces(env, home)) {
|
|
9716
|
+
const path2 = (0, import_node_path4.join)(workspace, ".agents", "skills", ".mmi-managed.json");
|
|
9717
|
+
if (!(0, import_node_fs7.existsSync)(path2)) continue;
|
|
9718
|
+
try {
|
|
9719
|
+
const record2 = JSON.parse((0, import_node_fs7.readFileSync)(path2, "utf8"));
|
|
9720
|
+
return { workspace, ...typeof record2.version === "string" ? { version: record2.version } : {} };
|
|
9721
|
+
} catch {
|
|
9722
|
+
return { workspace };
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
return null;
|
|
9633
9726
|
}
|
|
9634
|
-
function
|
|
9635
|
-
|
|
9727
|
+
function openbotWorkspaceSkillsHealthy(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9728
|
+
const record2 = openbotManagedRecord(env, home);
|
|
9729
|
+
if (!record2) return false;
|
|
9636
9730
|
try {
|
|
9637
|
-
|
|
9731
|
+
const recordFile = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path4.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json"), "utf8"));
|
|
9732
|
+
const owned = Array.isArray(recordFile.skills) ? recordFile.skills : [];
|
|
9733
|
+
return owned.length > 0 && owned.every((name) => typeof name === "string" && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(record2.workspace, ".agents", "skills", name, "SKILL.md")));
|
|
9638
9734
|
} catch {
|
|
9639
9735
|
return false;
|
|
9640
9736
|
}
|
|
@@ -9649,10 +9745,11 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
9649
9745
|
// Codex's native projection is authoritative; its legacy installed_plugins.json may be empty
|
|
9650
9746
|
// while `plugin list --json` proves the managed plugin installed (#6111).
|
|
9651
9747
|
installRecordPresent: surface === "codex" ? codexStatus?.installed === true : hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
|
|
9652
|
-
surface === "jervcode" && piEntry !== null ||
|
|
9748
|
+
surface === "jervcode" && piEntry !== null || // #9130: OpenBot's install record is the workspace ownership file MMI itself writes.
|
|
9749
|
+
surface === "openbot" && openbotManagedRecord() !== null,
|
|
9653
9750
|
// JervCode's settings entry and native local surfaces have no marketplace clone.
|
|
9654
|
-
marketplaceClonePresent: surface === "jervcode" || surface === "
|
|
9655
|
-
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "
|
|
9751
|
+
marketplaceClonePresent: surface === "jervcode" || surface === "openbot" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
|
|
9752
|
+
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "openbot" ? openbotWorkspaceSkillsHealthy() : surface === "codex" ? Boolean(
|
|
9656
9753
|
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
|
|
9657
9754
|
) : (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
9658
9755
|
};
|
|
@@ -9951,7 +10048,6 @@ var SESSION_ID_ENV_VARS = [
|
|
|
9951
10048
|
"CLAUDE_CODE_SESSION_ID",
|
|
9952
10049
|
"CODEX_SESSION_ID",
|
|
9953
10050
|
"CODEX_THREAD_ID",
|
|
9954
|
-
"HERMES_SESSION_ID",
|
|
9955
10051
|
"PI_SESSION_ID",
|
|
9956
10052
|
// #6193: lane runners spawn the CLI through the host's extension shell operations, which strip
|
|
9957
10053
|
// the PI_* session variables; the JervCode launcher's durable lane id (JERV_SESSION_ID) is what
|
|
@@ -9960,6 +10056,9 @@ var SESSION_ID_ENV_VARS = [
|
|
|
9960
10056
|
// synth/shell identity and the claim guard contests the caller's OWN lane (the #3414/#3438
|
|
9961
10057
|
// refusals of jerv_worktree_land's `devops pr create` from the claimed task worktree).
|
|
9962
10058
|
"JERV_SESSION_ID"
|
|
10059
|
+
// #9130: OpenBot exports no session variable of its own (its surface comes from the agent-workspace
|
|
10060
|
+
// cwd). Merv's codex runtime carries CODEX_THREAD_ID, honoured above; otherwise an OpenBot claim's
|
|
10061
|
+
// session is the per-process `synth-openbot-…` fallback — it proves the surface, never one lane.
|
|
9963
10062
|
];
|
|
9964
10063
|
var SYNTH_SESSION_PREFIX = "synth-";
|
|
9965
10064
|
function isHostSessionId(session) {
|
|
@@ -16032,6 +16131,9 @@ function partitionByRequired(entries, requiredContexts) {
|
|
|
16032
16131
|
ignoredFailures.push(entry.name);
|
|
16033
16132
|
}
|
|
16034
16133
|
}
|
|
16134
|
+
if (relevant.length && [...requiredContexts].some((name) => !entries.some((entry) => entry.name === name))) {
|
|
16135
|
+
relevant.push("pending");
|
|
16136
|
+
}
|
|
16035
16137
|
return { relevant, ignoredFailures };
|
|
16036
16138
|
}
|
|
16037
16139
|
function parseNdjsonLines(stdout) {
|
|
@@ -18735,6 +18837,7 @@ function shouldWithdrawSurfaceFlag(result) {
|
|
|
18735
18837
|
|
|
18736
18838
|
// src/secrets-transport.ts
|
|
18737
18839
|
init_client_version();
|
|
18840
|
+
init_hub_auth();
|
|
18738
18841
|
var OWNER = "mutmutco";
|
|
18739
18842
|
var SSM_ROOT = "/mmi-future";
|
|
18740
18843
|
var ORG_INFRA_SLUG = "_org";
|
|
@@ -18896,6 +18999,17 @@ function errorDetail(body) {
|
|
|
18896
18999
|
const error = typeof body.error === "string" ? body.error : "";
|
|
18897
19000
|
return error ? `: ${error}` : "";
|
|
18898
19001
|
}
|
|
19002
|
+
function hubAuthErrorDetail(body) {
|
|
19003
|
+
const parts = [errorDetail(body)];
|
|
19004
|
+
if (safeServerReason(body.reason)) parts.push(` (reason: ${String(body.reason)})`);
|
|
19005
|
+
if (safeServerReason(body.hint)) parts.push(` \u2014 ${String(body.hint)}`);
|
|
19006
|
+
return parts.join("");
|
|
19007
|
+
}
|
|
19008
|
+
function refusedLine(prefix, res, body) {
|
|
19009
|
+
const local = lastHubAuthFailure();
|
|
19010
|
+
if (res.status === 401 && local) return local.message;
|
|
19011
|
+
return `${prefix}: HTTP ${res.status}${hubAuthErrorDetail(body)}`;
|
|
19012
|
+
}
|
|
18899
19013
|
async function upgradeMessage(res, body) {
|
|
18900
19014
|
if (res.status !== 426) return null;
|
|
18901
19015
|
return upgradeRequiredError(res, body ?? await readJsonBody(res));
|
|
@@ -18944,7 +19058,7 @@ async function secretsList(deps, opts) {
|
|
|
18944
19058
|
return;
|
|
18945
19059
|
}
|
|
18946
19060
|
if (!res.ok) {
|
|
18947
|
-
deps.err(await upgradeMessage(res) ??
|
|
19061
|
+
deps.err(await upgradeMessage(res) ?? refusedLine("secrets list failed", res, await readJsonBody(res)));
|
|
18948
19062
|
return;
|
|
18949
19063
|
}
|
|
18950
19064
|
const { secrets } = await res.json();
|
|
@@ -19329,6 +19443,10 @@ function missingRequiredKeys(stage, required, present, declaredSecrets) {
|
|
|
19329
19443
|
}
|
|
19330
19444
|
async function secretsPreflight(deps, opts) {
|
|
19331
19445
|
const repo = await targetRepo(deps, opts);
|
|
19446
|
+
if (opts.required.length === 0 && !opts.json) {
|
|
19447
|
+
deps.log(`0 required ${opts.stage} secret names`);
|
|
19448
|
+
return true;
|
|
19449
|
+
}
|
|
19332
19450
|
const qs = new URLSearchParams({ repo }).toString();
|
|
19333
19451
|
let res;
|
|
19334
19452
|
try {
|
|
@@ -20265,7 +20383,7 @@ async function secretsRequest(deps, key, opts) {
|
|
|
20265
20383
|
});
|
|
20266
20384
|
const body = await readJsonBody(res);
|
|
20267
20385
|
if (!res.ok) {
|
|
20268
|
-
deps.err(await upgradeMessage(res, body) ??
|
|
20386
|
+
deps.err(await upgradeMessage(res, body) ?? refusedLine("secrets request failed", res, body));
|
|
20269
20387
|
return false;
|
|
20270
20388
|
}
|
|
20271
20389
|
if (opts.json) {
|
|
@@ -20962,7 +21080,7 @@ async function fetchSecretForUse(deps, { repo, key, slug }) {
|
|
|
20962
21080
|
return null;
|
|
20963
21081
|
}
|
|
20964
21082
|
deps.err(
|
|
20965
|
-
await upgradeMessage(res, body) ?? (res.status === 403 ? `secrets use: not authorized for ${key} (HTTP 403) \u2014 ask the master for a \`secrets grant\`${errorDetail(body)}` :
|
|
21083
|
+
await upgradeMessage(res, body) ?? (res.status === 403 ? `secrets use: not authorized for ${key} (HTTP 403) \u2014 ask the master for a \`secrets grant\`${errorDetail(body)}` : refusedLine("secrets use failed", res, body))
|
|
20966
21084
|
);
|
|
20967
21085
|
return null;
|
|
20968
21086
|
}
|
|
@@ -24970,6 +25088,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
24970
25088
|
// src/gate-budget.ts
|
|
24971
25089
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
24972
25090
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25091
|
+
var BLESSED_KIT_AGENT_FILES_SHA = "a8b273ed418e703c0aa501a53ea87228ba28b1f0";
|
|
24973
25092
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
24974
25093
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
24975
25094
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25345,6 +25464,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25345
25464
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
25346
25465
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
25347
25466
|
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
|
|
25467
|
+
// #9161: the suite-only kit-file drift action arrived after the shared runner pin, so it carries its own
|
|
25468
|
+
// blessed revision — bumping GATE_RUNNER_SHA for it would move every generic repo's toolchain too.
|
|
25469
|
+
KIT_AGENT_FILES_SHA: BLESSED_KIT_AGENT_FILES_SHA,
|
|
25348
25470
|
// #8129: the guard and its bundled CLI must come from the same immutable Hub revision.
|
|
25349
25471
|
GATE_RUNBOOK_SHA: BLESSED_RUNBOOK_GUARD_SHA,
|
|
25350
25472
|
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
@@ -27088,10 +27210,10 @@ var rollout_plan_default = {
|
|
|
27088
27210
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
27089
27211
|
},
|
|
27090
27212
|
baseline: {
|
|
27091
|
-
version: "4.5.
|
|
27092
|
-
tag: "v4.5.
|
|
27093
|
-
commit: "
|
|
27094
|
-
npm: "@mutmutco/cli@4.5.
|
|
27213
|
+
version: "4.5.228",
|
|
27214
|
+
tag: "v4.5.228",
|
|
27215
|
+
commit: "097f7c9ad0e0",
|
|
27216
|
+
npm: "@mutmutco/cli@4.5.228"
|
|
27095
27217
|
},
|
|
27096
27218
|
exitCriterion: "fleet-n-of-n",
|
|
27097
27219
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27108,14 +27230,14 @@ var rollout_plan_default = {
|
|
|
27108
27230
|
repo: "mutmutco/mmi-hub",
|
|
27109
27231
|
role: "canary",
|
|
27110
27232
|
schedule: "train",
|
|
27111
|
-
v3Target: "v4.5.
|
|
27233
|
+
v3Target: "v4.5.228"
|
|
27112
27234
|
}
|
|
27113
27235
|
],
|
|
27114
27236
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
27115
27237
|
rollback: {
|
|
27116
27238
|
independent: true,
|
|
27117
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
27118
|
-
v3Target: "v4.5.
|
|
27239
|
+
mechanism: "npm dist-tag latest -> 4.5.228 and redeploy the Hub Lambda from tag v4.5.228 (097f7c9ad0e0); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27240
|
+
v3Target: "v4.5.228 (@mutmutco/cli@4.5.228, tag commit 097f7c9ad0e0 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27119
27241
|
}
|
|
27120
27242
|
},
|
|
27121
27243
|
{
|
|
@@ -29272,7 +29394,8 @@ async function buildTrainApplyContext(deps) {
|
|
|
29272
29394
|
repo,
|
|
29273
29395
|
owner,
|
|
29274
29396
|
slug: name.toLowerCase(),
|
|
29275
|
-
login
|
|
29397
|
+
login,
|
|
29398
|
+
...verdict.authenticatedDelegation === true ? { delegatedActor: login } : {}
|
|
29276
29399
|
};
|
|
29277
29400
|
}
|
|
29278
29401
|
function commandAuthorityLabel(owner) {
|
|
@@ -32049,57 +32172,24 @@ function installedClaudePluginVersion() {
|
|
|
32049
32172
|
return void 0;
|
|
32050
32173
|
}
|
|
32051
32174
|
}
|
|
32052
|
-
function
|
|
32053
|
-
const
|
|
32054
|
-
const
|
|
32055
|
-
const
|
|
32056
|
-
const manifestPath = (0, import_node_path22.join)(root, "plugin.yaml");
|
|
32057
|
-
let installedVersion;
|
|
32175
|
+
function readOpenBotPluginEvidence(env = process.env) {
|
|
32176
|
+
const workspaces = openbotAgentWorkspaces(env);
|
|
32177
|
+
const record2 = openbotManagedRecord(env);
|
|
32178
|
+
const recordPath = record2 ? (0, import_node_path22.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json") : void 0;
|
|
32058
32179
|
let manifest = "missing";
|
|
32059
|
-
|
|
32060
|
-
const text = (0, import_node_fs25.readFileSync)(manifestPath, "utf8");
|
|
32061
|
-
let version;
|
|
32062
|
-
try {
|
|
32063
|
-
const parsed = JSON.parse(text).version;
|
|
32064
|
-
if (typeof parsed === "string" && parsed.trim()) version = parsed.trim();
|
|
32065
|
-
} catch {
|
|
32066
|
-
}
|
|
32067
|
-
if (!version) {
|
|
32068
|
-
const match = /^version:\s*["']?([^\s"'#]+)["']?\s*(?:#.*)?$/m.exec(text);
|
|
32069
|
-
version = match?.[1];
|
|
32070
|
-
}
|
|
32071
|
-
if (version) {
|
|
32072
|
-
installedVersion = version;
|
|
32073
|
-
manifest = "valid";
|
|
32074
|
-
} else if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32075
|
-
} catch {
|
|
32076
|
-
if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32077
|
-
}
|
|
32078
|
-
let skills = false;
|
|
32079
|
-
try {
|
|
32080
|
-
skills = (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, "skills")) && (0, import_node_fs25.statSync)((0, import_node_path22.join)(root, "skills")).isDirectory();
|
|
32081
|
-
} catch {
|
|
32082
|
-
}
|
|
32083
|
-
let provisioned = false;
|
|
32084
|
-
const provisionedRoot = (0, import_node_path22.join)(host, "skills", "mmi");
|
|
32085
|
-
try {
|
|
32086
|
-
provisioned = (0, import_node_fs25.existsSync)(provisionedRoot) && (0, import_node_fs25.statSync)(provisionedRoot).isDirectory();
|
|
32087
|
-
} catch {
|
|
32088
|
-
}
|
|
32089
|
-
const treeOk = (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, "__init__.py")) && skills && manifest === "valid";
|
|
32090
|
-
const receipt = treeOk && !provisioned ? `hermes-skills-unprovisioned:${provisionedRoot}` : void 0;
|
|
32180
|
+
if (recordPath) manifest = record2?.version ? "valid" : "invalid";
|
|
32091
32181
|
return {
|
|
32092
|
-
hostPresent:
|
|
32093
|
-
installRecordPresent,
|
|
32182
|
+
hostPresent: workspaces.length > 0,
|
|
32183
|
+
installRecordPresent: record2 !== null,
|
|
32094
32184
|
manifest,
|
|
32095
|
-
payloadPresent:
|
|
32096
|
-
...
|
|
32097
|
-
...
|
|
32185
|
+
payloadPresent: record2 !== null && openbotWorkspaceSkillsHealthy(env),
|
|
32186
|
+
...record2?.version ? { installedVersion: record2.version } : {},
|
|
32187
|
+
...recordPath && !record2?.version ? { receipt: `openbot-ownership-unreadable:${recordPath}` } : {}
|
|
32098
32188
|
};
|
|
32099
32189
|
}
|
|
32100
32190
|
function installedSurfacePluginVersion(surface) {
|
|
32101
32191
|
const token = surfaceToken(surface);
|
|
32102
|
-
if (token === "
|
|
32192
|
+
if (token === "openbot") return readOpenBotPluginEvidence().installedVersion;
|
|
32103
32193
|
if (token === "jervcode") {
|
|
32104
32194
|
return installedJervCodePackageVersion();
|
|
32105
32195
|
}
|
|
@@ -34298,6 +34388,22 @@ function supersedeablePublishLegShape(rec) {
|
|
|
34298
34388
|
function deferredGatewayDeployLegShape(rec) {
|
|
34299
34389
|
return rec.state === "pending" && rec.runId == null && rec.workflow === "jerv-gateway";
|
|
34300
34390
|
}
|
|
34391
|
+
function releaseAssetsCarryInstallerPayload(assets, tag) {
|
|
34392
|
+
const version = tag.replace(/^v/, "");
|
|
34393
|
+
return assets.some((asset) => typeof asset.name === "string" && asset.name.startsWith("installer-payload-") && asset.name.endsWith(`-${version}.tar.gz`));
|
|
34394
|
+
}
|
|
34395
|
+
async function proveInstallerPayloadPresence(deps, prior, label, ref) {
|
|
34396
|
+
let assets;
|
|
34397
|
+
try {
|
|
34398
|
+
const parsed = JSON.parse(await deps.run("gh", ["release", "view", prior.tag, "--repo", prior.repo, "--json", "assets"]));
|
|
34399
|
+
assets = Array.isArray(parsed.assets) ? parsed.assets : [];
|
|
34400
|
+
} catch (e) {
|
|
34401
|
+
throw new Error(
|
|
34402
|
+
`${label}: could not read the ${prior.tag} GitHub Release asset list (${e instanceof Error ? e.message.split("\n")[0] : String(e)}) \u2014 whether the installer payload exists is unproven, so the deploy leg cannot be closed; retry when GitHub answers; nothing was written (${ref})`
|
|
34403
|
+
);
|
|
34404
|
+
}
|
|
34405
|
+
return releaseAssetsCarryInstallerPayload(assets, prior.tag) ? "present" : "absent";
|
|
34406
|
+
}
|
|
34301
34407
|
async function proveOriginPromotionOrArchived(deps, prior, path2, label, ref, fence) {
|
|
34302
34408
|
let liveSha = "";
|
|
34303
34409
|
try {
|
|
@@ -34383,23 +34489,29 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
|
34383
34489
|
};
|
|
34384
34490
|
}
|
|
34385
34491
|
if (deferredGateway) {
|
|
34386
|
-
|
|
34387
|
-
|
|
34388
|
-
prior.
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
if (dispatch.deployStatus !== "success") {
|
|
34398
|
-
throw new Error(
|
|
34399
|
-
`release --supersede-publish: the deferred ${prior.tag} Jerv Gateway operator-host deploy leg did not verify (${dispatch.note}) \u2014 run \`scripts/jerv-gateway-release-deploy.sh --tag ${prior.tag}\` to a healthy receipt naming the released tag and commit, then retry; nothing was written (#6742)`
|
|
34492
|
+
if (prior.phases.publish.state === "failed" && await proveInstallerPayloadPresence(deps, prior, "release --supersede-publish", "#9182") === "absent") {
|
|
34493
|
+
const payloadNote = `superseded: the ${prior.tag} publish never produced the installer payload (the live release carries no installer-payload asset for this version) \u2014 the deferred Jerv Gateway operator-host deploy leg was skipped, nothing was deployable from this tag; the cause fix rides the next patch train (#9182)`;
|
|
34494
|
+
phases.deploy = { state: "skipped", sha: prior.tagSha, superseded: true, note: payloadNote, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
34495
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "skipped", note: payloadNote };
|
|
34496
|
+
} else {
|
|
34497
|
+
const dispatch = await appendJervGatewayReleaseDeploy(
|
|
34498
|
+
deps,
|
|
34499
|
+
prior.repo,
|
|
34500
|
+
prior.tag,
|
|
34501
|
+
prior.tagSha,
|
|
34502
|
+
{ note: `Jerv Gateway operator-host deploy leg for ${prior.tag} run during the publish supersession`, deployStatus: "success" }
|
|
34400
34503
|
);
|
|
34504
|
+
phases.deploy = {
|
|
34505
|
+
...phaseEntry(deployPhaseInput("registry-publish", dispatch, { releaseSha: prior.tagSha })),
|
|
34506
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
34507
|
+
};
|
|
34508
|
+
if (dispatch.deployStatus !== "success") {
|
|
34509
|
+
throw new Error(
|
|
34510
|
+
`release --supersede-publish: the deferred ${prior.tag} Jerv Gateway operator-host deploy leg did not verify (${dispatch.note}) \u2014 run \`scripts/jerv-gateway-release-deploy.sh --tag ${prior.tag}\` to a healthy receipt naming the released tag and commit, then retry; nothing was written (#6742)`
|
|
34511
|
+
);
|
|
34512
|
+
}
|
|
34513
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
|
|
34401
34514
|
}
|
|
34402
|
-
gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
|
|
34403
34515
|
}
|
|
34404
34516
|
const note = `superseded: content-caused publish failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6638)`;
|
|
34405
34517
|
await fence(() => atomicWriteJson(path2, {
|
|
@@ -34483,7 +34595,11 @@ async function supersedeContentCausedDeployAndPublishLegs(deps, cwd) {
|
|
|
34483
34595
|
}
|
|
34484
34596
|
let deployEntry;
|
|
34485
34597
|
let gatewayDeploy;
|
|
34486
|
-
if (deferredGateway) {
|
|
34598
|
+
if (deferredGateway && publish.state === "failed" && await proveInstallerPayloadPresence(deps, prior, label, "#9182") === "absent") {
|
|
34599
|
+
const payloadNote = `superseded: the ${prior.tag} publish never produced the installer payload (the live release carries no installer-payload asset for this version) \u2014 the deferred Jerv Gateway operator-host deploy leg was skipped, nothing was deployable from this tag; the cause fix rides the next patch train (#9182)`;
|
|
34600
|
+
deployEntry = { state: "skipped", sha: prior.tagSha, superseded: true, note: payloadNote, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
34601
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "skipped", note: payloadNote };
|
|
34602
|
+
} else if (deferredGateway) {
|
|
34487
34603
|
const dispatch = await appendJervGatewayReleaseDeploy(
|
|
34488
34604
|
deps,
|
|
34489
34605
|
prior.repo,
|
|
@@ -37401,7 +37517,15 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37401
37517
|
);
|
|
37402
37518
|
}
|
|
37403
37519
|
await requireCleanTree(deps);
|
|
37404
|
-
|
|
37520
|
+
const ciCheckout = options.ciCheckout === true;
|
|
37521
|
+
if (ciCheckout) {
|
|
37522
|
+
if (!isHubControlRepo(ctx.repo)) {
|
|
37523
|
+
throw new Error(`release --abort: the CI checkout path aborts ${ctx.repo} never; it is for the Hub release workflow only; nothing was written`);
|
|
37524
|
+
}
|
|
37525
|
+
if (!ctx.delegatedActor) {
|
|
37526
|
+
throw new Error("release --abort: the CI checkout path needs the run's proven org-owner trigger (hub release workflow authority); nothing was written");
|
|
37527
|
+
}
|
|
37528
|
+
} else if (await currentBranch(deps) !== "main") {
|
|
37405
37529
|
throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
|
|
37406
37530
|
}
|
|
37407
37531
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
@@ -37411,7 +37535,7 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37411
37535
|
const tagSha = await probeRemoteTag(deps, tag);
|
|
37412
37536
|
if (!tagSha) throw new Error(`release --abort: ${tag} is not on origin; nothing was written`);
|
|
37413
37537
|
const localTagSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
37414
|
-
const localMainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37538
|
+
const localMainSha = ciCheckout ? tagSha : clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37415
37539
|
const originMainSha = clean2(await deps.run("git", ["rev-parse", "origin/main"]));
|
|
37416
37540
|
if (localTagSha !== tagSha || localMainSha !== tagSha) {
|
|
37417
37541
|
throw new Error(
|
|
@@ -37442,6 +37566,17 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37442
37566
|
throw new Error(`release --abort: origin still reports ${tag} after deletion; local state was preserved`);
|
|
37443
37567
|
}
|
|
37444
37568
|
await deps.run("git", ["tag", "-d", tag]);
|
|
37569
|
+
if (ciCheckout) {
|
|
37570
|
+
const ciLedgerNote = await clearReleaseLedger(process.cwd(), { repo: ctx.repo, tag, tagSha });
|
|
37571
|
+
return {
|
|
37572
|
+
command: "release-abort",
|
|
37573
|
+
repo: ctx.repo,
|
|
37574
|
+
tag,
|
|
37575
|
+
tagSha,
|
|
37576
|
+
restoredMainSha: originMainSha,
|
|
37577
|
+
note: `removed the unpublished ${tag} candidate from the Hub release workflow; destructive approval: @${ctx.delegatedActor} triggered this run and is a proven active org owner; origin/main stays ${originMainSha.slice(0, 12)}; repair development before recutting` + (ciLedgerNote ? `; ${ciLedgerNote}` : "")
|
|
37578
|
+
};
|
|
37579
|
+
}
|
|
37445
37580
|
await deps.run("git", ["checkout", "development"]);
|
|
37446
37581
|
await ffOnlyPull(deps, "development");
|
|
37447
37582
|
await deps.run("git", ["branch", "-f", "main", "origin/main"]);
|
|
@@ -40783,7 +40918,7 @@ function registerBoardCommands(program3) {
|
|
|
40783
40918
|
return `Claimed ${ref} for ${holder} - In Progress${reclaimed}`;
|
|
40784
40919
|
}
|
|
40785
40920
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
40786
|
-
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--snapshot-only", "never fall back to your personal GraphQL pool; failure exits: 20 hub-unreachable, 21 app-quota-exhausted, 22 refused. With --json, stdout is {ok:false,outcome,reason[,resetAt,pool]} (pool is rest or graphql when known); success is unchanged").option("--min-cursor <n>", "board-event cursor (from `oracle board events`) the Hub snapshot must postdate; a cached snapshot older than that event is rebuilt (#7240)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
40921
|
+
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--snapshot-only", "never fall back to your personal GraphQL pool; failure exits: 20 hub-unreachable, 21 app-quota-exhausted, 22 refused. With --json, stdout is {ok:false,outcome,reason[,resetAt,pool]} (pool is rest or graphql when known) and the human-readable reason goes to stderr; success is unchanged").option("--min-cursor <n>", "board-event cursor (from `oracle board events`) the Hub snapshot must postdate; a cached snapshot older than that event is rebuilt (#7240)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n").action((o) => runBoardRead(o));
|
|
40787
40922
|
board.command("events").description("content-free board/CI change records after a cursor, from the Hub event feed (no GitHub call) \u2014 for real-time sidebars (#7235)").option("--since <cursor>", "cursor from the previous answer; omit on first read (answers the head cursor with resync:true)").option("--wait <seconds>", "hold up to this long (max 120), polling every 2s, and return on the first event or resync", "0").option("--repo <owner/repo>", "repo whose Hub config to use (defaults to git origin)").option("--json", "machine-readable output (already the default)").addHelpText("after", "\nPrints {cursor, events, resync, more[, hidden]}. Pass the returned cursor as the next --since.\nresync:true means records were missed (retention ~1h, or a gap): re-read the board with\n`mmi-cli oracle board read --snapshot-only --json`.\n").action(async (o) => {
|
|
40788
40923
|
try {
|
|
40789
40924
|
if (o.since !== void 0 && !/^\d{1,15}$/.test(o.since)) throw new Error("--since must be a non-negative integer cursor");
|
|
@@ -43003,7 +43138,7 @@ function registerSecretsCommands(program3) {
|
|
|
43003
43138
|
}
|
|
43004
43139
|
const { keys, command, ...o } = parsed.value;
|
|
43005
43140
|
const code = secretsUseExitCode(await secretsUse(d, keys, { ...o, command }));
|
|
43006
|
-
if (code !== void 0) process.exitCode = code;
|
|
43141
|
+
if (code !== void 0) process.exitCode = lastHubAuthFailure()?.exitCode ?? code;
|
|
43007
43142
|
});
|
|
43008
43143
|
});
|
|
43009
43144
|
withDeclaredOptions(
|
|
@@ -49623,15 +49758,40 @@ var PR_LAND_STATE_READ_RETRIES = 3;
|
|
|
49623
49758
|
var PR_LAND_STATE_READ_DELAY_MS = 15e3;
|
|
49624
49759
|
var PR_LAND_MERGE_RETRY_DELAY_MS = 3e4;
|
|
49625
49760
|
async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
|
|
49626
|
-
const
|
|
49627
|
-
|
|
49628
|
-
const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
|
|
49629
|
-
if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
|
|
49761
|
+
const now = deps.now ?? (() => Date.now());
|
|
49762
|
+
const deadline = now() + PR_LAND_ENQUEUE_TIMEOUT_MS;
|
|
49630
49763
|
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
49631
|
-
await
|
|
49632
|
-
|
|
49633
|
-
|
|
49634
|
-
|
|
49764
|
+
const first = await deps.mergeAuto(prNumber, repo);
|
|
49765
|
+
let result = first;
|
|
49766
|
+
let transientRetried = false;
|
|
49767
|
+
while (result.mergeStatus === "failed") {
|
|
49768
|
+
const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false, checksPending: false }));
|
|
49769
|
+
if (ready.open && ready.checksPending) {
|
|
49770
|
+
if (now() >= deadline) break;
|
|
49771
|
+
await sleep2(Math.min(PR_LAND_POLL_MS, deadline - now()));
|
|
49772
|
+
if (now() >= deadline) break;
|
|
49773
|
+
const checks = await deps.waitForChecks(prNumber, repo, deadline - now());
|
|
49774
|
+
if (checks.status !== "success" && checks.status !== "skipped") {
|
|
49775
|
+
return { mergeStatus: "failed", error: `${result.error ?? "merge failed"}
|
|
49776
|
+
registration wait: checks-wait ${checks.status}: ${checks.detail ?? checks.reason ?? "unknown"}` };
|
|
49777
|
+
}
|
|
49778
|
+
if (now() >= deadline) break;
|
|
49779
|
+
} else {
|
|
49780
|
+
if (!ready.open || !ready.mergeable || !ready.checksPassing || transientRetried) return result;
|
|
49781
|
+
transientRetried = true;
|
|
49782
|
+
if (now() >= deadline) break;
|
|
49783
|
+
await sleep2(Math.min(PR_LAND_MERGE_RETRY_DELAY_MS, deadline - now()));
|
|
49784
|
+
if (now() >= deadline) break;
|
|
49785
|
+
}
|
|
49786
|
+
const retried = await deps.mergeAuto(prNumber, repo);
|
|
49787
|
+
result = retried.mergeStatus !== "failed" ? retried : {
|
|
49788
|
+
mergeStatus: "failed",
|
|
49789
|
+
error: `merge retry after transient failure also failed: first attempt: ${first.error ?? "unknown error"}
|
|
49790
|
+
retry: ${retried.error ?? "unknown error"}`
|
|
49791
|
+
};
|
|
49792
|
+
}
|
|
49793
|
+
return result.mergeStatus === "failed" ? { ...result, error: `${result.error ?? "merge failed"}
|
|
49794
|
+
required-check registration did not settle within the enqueue deadline` } : result;
|
|
49635
49795
|
}
|
|
49636
49796
|
var AUTO_MERGE_CONFIRM_RETRIES = 3;
|
|
49637
49797
|
var AUTO_MERGE_CONFIRM_DELAY_MS = 15e3;
|
|
@@ -50025,15 +50185,15 @@ function invisibleTrailer(sha, line) {
|
|
|
50025
50185
|
return {
|
|
50026
50186
|
kind: "malformed-override-trailer",
|
|
50027
50187
|
paths: [],
|
|
50028
|
-
detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)}
|
|
50029
|
-
|
|
50030
|
-
|
|
50031
|
-
|
|
50032
|
-
|
|
50033
|
-
|
|
50034
|
-
|
|
50035
|
-
|
|
50036
|
-
|
|
50188
|
+
detail: `OVERRIDE TRAILER GIT CANNOT SEE \u2014 commit ${sha.slice(0, 8)}
|
|
50189
|
+
Recovery (trailer): create a LATER commit on this branch with \`${TRAILER_KEY}: <reason>\`
|
|
50190
|
+
in the final trailer block, separated from the body by a blank line. Use \`Key: value\` lines
|
|
50191
|
+
only in that block; indent continuation lines and keep prose such as \`Closes #123\` above it.
|
|
50192
|
+
Verify: \`git log -1 --format='%(trailers:key=${TRAILER_KEY})' HEAD\` must show the waiver.
|
|
50193
|
+
Then run \`mmi-cli tests policy --json --base origin/development\` from the selected worktree root
|
|
50194
|
+
to refresh its receipt before retrying the focused test. Changing location alone cannot fix this trailer.
|
|
50195
|
+
Rejected line: ${line}
|
|
50196
|
+
Git's trailer parser reports no waiver; a regex-only reason is not git-auditable (#3628).
|
|
50037
50197
|
The nearest waiver wins, and it supersedes this one \u2014 no force-push, no re-filed branch.`
|
|
50038
50198
|
};
|
|
50039
50199
|
}
|
|
@@ -50911,9 +51071,8 @@ function ghFailureReason(e) {
|
|
|
50911
51071
|
const err = e;
|
|
50912
51072
|
const head = String(err?.message ?? "").split("\n")[0]?.trim() ?? "";
|
|
50913
51073
|
const detail = `${err?.stderr ?? ""}
|
|
50914
|
-
${err?.stdout ?? ""}`.split("\n").map((line) => line.trim()).
|
|
50915
|
-
|
|
50916
|
-
return detail || head || "unknown error";
|
|
51074
|
+
${err?.stdout ?? ""}`.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
51075
|
+
return (detail && head ? `${head} \u2014 ${detail}` : detail || head || "unknown error").replace(/\x1b\[[0-9;]*m/g, "").replace(/\b(?:gh[pousr]_|github_pat_|AKIA|sk-)[\w-]+/g, "[redacted]").replace(/\b(authorization:\s*)(?:[^\n]*)/gi, "$1[redacted]");
|
|
50917
51076
|
}
|
|
50918
51077
|
var defaultGhMergeAutoIo = {
|
|
50919
51078
|
gh: async (args, timeoutMs) => (await execFileP("gh", args, { timeout: timeoutMs })).stdout
|
|
@@ -51066,13 +51225,7 @@ async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile, me
|
|
|
51066
51225
|
error: stateRead.ok ? message2.split("\n")[0] : `could not confirm PR state after local branch cleanup warning: ${stateRead.error}`
|
|
51067
51226
|
};
|
|
51068
51227
|
}
|
|
51069
|
-
|
|
51070
|
-
return { mergeStatus: "failed", error: ghFailureReason(e) };
|
|
51071
|
-
}
|
|
51072
|
-
return {
|
|
51073
|
-
mergeStatus: "failed",
|
|
51074
|
-
error: `${ghFailureReason(e)} \u2014 auto-merge could not be armed yet (the PR's required checks have not registered); re-run the same --auto merge once they appear`
|
|
51075
|
-
};
|
|
51228
|
+
return { mergeStatus: "failed", error: ghFailureReason(e) };
|
|
51076
51229
|
}
|
|
51077
51230
|
return confirmEnqueueOutcome();
|
|
51078
51231
|
}
|
|
@@ -51225,6 +51378,61 @@ function prMergeQueueLine(result) {
|
|
|
51225
51378
|
// src/command-register-train-operations.ts
|
|
51226
51379
|
init_github_client();
|
|
51227
51380
|
|
|
51381
|
+
// src/hub-release-ci-authority.ts
|
|
51382
|
+
var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
|
|
51383
|
+
var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
|
|
51384
|
+
var HUB_RELEASE_REF = "refs/heads/development";
|
|
51385
|
+
var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
|
|
51386
|
+
var LOGIN = /^[A-Za-z0-9-]{1,39}$/;
|
|
51387
|
+
var ID = /^[1-9][0-9]*$/;
|
|
51388
|
+
function hubReleaseCiRun(env) {
|
|
51389
|
+
if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
|
|
51390
|
+
if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
|
|
51391
|
+
if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
|
|
51392
|
+
const runId = env.GITHUB_RUN_ID ?? "";
|
|
51393
|
+
const runAttempt = env.GITHUB_RUN_ATTEMPT ?? "";
|
|
51394
|
+
if (!ID.test(runId) || !ID.test(runAttempt)) return null;
|
|
51395
|
+
return { runId: Number(runId), runAttempt: Number(runAttempt) };
|
|
51396
|
+
}
|
|
51397
|
+
async function hubReleaseCiAuthority(repo, deps) {
|
|
51398
|
+
const run = hubReleaseCiRun(deps.env);
|
|
51399
|
+
if (!run) return null;
|
|
51400
|
+
const refuse = (check) => ({ ok: false, error: `hub release workflow authority refused: ${check}` });
|
|
51401
|
+
if (repo.toLowerCase() !== HUB_RELEASE_REPO.toLowerCase()) return refuse(`this workflow releases ${HUB_RELEASE_REPO} only`);
|
|
51402
|
+
let live;
|
|
51403
|
+
try {
|
|
51404
|
+
live = await deps.getJson(`repos/${HUB_RELEASE_REPO}/actions/runs/${run.runId}`);
|
|
51405
|
+
} catch (e) {
|
|
51406
|
+
return refuse(`live run read failed (${e instanceof Error ? e.message : String(e)})`);
|
|
51407
|
+
}
|
|
51408
|
+
const login = live?.triggering_actor?.login ?? "";
|
|
51409
|
+
const bound = live?.id === run.runId && live.run_attempt === run.runAttempt && live.status === "in_progress" && live.event === "workflow_dispatch" && live.path === HUB_RELEASE_WORKFLOW && live.head_branch === "development" && live.repository?.full_name === HUB_RELEASE_REPO;
|
|
51410
|
+
if (!bound) return refuse("live run binding");
|
|
51411
|
+
if (live.triggering_actor?.type !== "User" || !LOGIN.test(login)) return refuse("the run was not triggered by a person");
|
|
51412
|
+
let membership;
|
|
51413
|
+
try {
|
|
51414
|
+
membership = await deps.getJson(`orgs/mutmutco/memberships/${login}`);
|
|
51415
|
+
} catch (e) {
|
|
51416
|
+
return refuse(`org membership read for ${login} failed (${e instanceof Error ? e.message : String(e)})`);
|
|
51417
|
+
}
|
|
51418
|
+
if (membership?.role !== "admin" || membership?.state !== "active") return refuse(`${login} is not an active org owner`);
|
|
51419
|
+
return { ok: true, role: "master", train: true, authenticatedDelegation: true, delegatedActor: login };
|
|
51420
|
+
}
|
|
51421
|
+
var GITHUB_API = "https://api.github.com";
|
|
51422
|
+
function pinnedGitHubGetJson(token, fetchImpl = fetch) {
|
|
51423
|
+
return async (path2) => {
|
|
51424
|
+
if (!token) throw new Error("GH_TOKEN is not set in this run");
|
|
51425
|
+
if (!/^[A-Za-z0-9_.\/-]+$/.test(path2) || path2.includes("..")) throw new Error("refusing an unexpected API path");
|
|
51426
|
+
const res = await fetchImpl(`${GITHUB_API}/${path2}`, {
|
|
51427
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" },
|
|
51428
|
+
signal: AbortSignal.timeout(15e3)
|
|
51429
|
+
});
|
|
51430
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
51431
|
+
return res.json();
|
|
51432
|
+
};
|
|
51433
|
+
}
|
|
51434
|
+
var HUB_RELEASE_CI_ANNOUNCE_DEFERRED = "announcement deferred: the Hub cannot authorize a release-workflow session to post yet (#9138); the curated summary is in this run's step summary";
|
|
51435
|
+
|
|
51228
51436
|
// src/project-info-sync.ts
|
|
51229
51437
|
var import_node_fs38 = require("node:fs");
|
|
51230
51438
|
var import_node_path36 = require("node:path");
|
|
@@ -56773,9 +56981,10 @@ ${list}`);
|
|
|
56773
56981
|
},
|
|
56774
56982
|
fetchTrainAuthority: async (repo) => fetchTrainAuthority(repo, registryClientDeps(await loadConfig())),
|
|
56775
56983
|
resolveCiPolicy: (repo) => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
56776
|
-
waitForChecks: async (prNumber, repo) => {
|
|
56984
|
+
waitForChecks: async (prNumber, repo, timeoutMs) => {
|
|
56777
56985
|
const requiredContexts = await fetchRequiredCheckContexts(repo, "development").catch(() => null);
|
|
56778
56986
|
return waitForPrChecks({
|
|
56987
|
+
timeoutMs,
|
|
56779
56988
|
resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
|
|
56780
56989
|
// #3024: REST-only wait loop — runPrLand's resolveRepo already guarantees `repo` is set.
|
|
56781
56990
|
pollChecks: () => pollRestPrChecks(prNumber, repo, void 0, requiredContexts),
|
|
@@ -56817,7 +57026,8 @@ ${list}`);
|
|
|
56817
57026
|
return {
|
|
56818
57027
|
open: snapshot?.state === "open",
|
|
56819
57028
|
mergeable: snapshot?.mergeable === "MERGEABLE",
|
|
56820
|
-
checksPassing: checks === "success"
|
|
57029
|
+
checksPassing: checks === "success",
|
|
57030
|
+
checksPending: checks === "pending" || checks === "failing" || checks === "no-checks-reported"
|
|
56821
57031
|
};
|
|
56822
57032
|
},
|
|
56823
57033
|
readPrState: async (prNumber, repo) => {
|
|
@@ -56839,21 +57049,21 @@ ${list}`);
|
|
|
56839
57049
|
// #8456: a pinned land has no update lane at all.
|
|
56840
57050
|
...o.expectHead ? {} : { updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)) },
|
|
56841
57051
|
pollMerged: async (prNumber, repo, deadlineMs) => {
|
|
56842
|
-
let
|
|
57052
|
+
let lastFailure2;
|
|
56843
57053
|
while (Date.now() < deadlineMs) {
|
|
56844
57054
|
const read = await pollRestPrMerged(prNumber, repo);
|
|
56845
57055
|
if (read.state === "merged") return true;
|
|
56846
57056
|
if (read.state === "failed") {
|
|
56847
|
-
|
|
57057
|
+
lastFailure2 = read.error;
|
|
56848
57058
|
console.warn(`pr land: merged-state read FAILED (${read.error}) \u2014 retrying; this is not evidence the PR is unmerged`);
|
|
56849
57059
|
} else {
|
|
56850
|
-
|
|
57060
|
+
lastFailure2 = void 0;
|
|
56851
57061
|
}
|
|
56852
57062
|
await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
|
|
56853
57063
|
}
|
|
56854
|
-
if (
|
|
57064
|
+
if (lastFailure2) {
|
|
56855
57065
|
throw new Error(
|
|
56856
|
-
`pr land: could not verify whether PR #${prNumber} merged \u2014 the last merged-state read FAILED (${
|
|
57066
|
+
`pr land: could not verify whether PR #${prNumber} merged \u2014 the last merged-state read FAILED (${lastFailure2}). The auto-merge enqueue stands; read the PR directly (gh pr view ${prNumber} --repo ${repo} --json merged,state) and rerun cleanup if it merged.`
|
|
56857
57067
|
);
|
|
56858
57068
|
}
|
|
56859
57069
|
return false;
|
|
@@ -57516,6 +57726,8 @@ function trainApplyDeps() {
|
|
|
57516
57726
|
// parses, so the ONE self-spawn seam derives the prefix through the same rule the refusal names.
|
|
57517
57727
|
runSelf: async (args) => (await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
|
|
57518
57728
|
trainAuthority: async (repo) => {
|
|
57729
|
+
const ci2 = await hubReleaseCiAuthority(repo, { env: process.env, getJson: pinnedGitHubGetJson(process.env.GH_TOKEN) });
|
|
57730
|
+
if (ci2) return ci2;
|
|
57519
57731
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
57520
57732
|
return verdict.ok ? {
|
|
57521
57733
|
ok: true,
|
|
@@ -57586,6 +57798,7 @@ function trainApplyDeps() {
|
|
|
57586
57798
|
return (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS, ...env ? { env } : {} })).stdout;
|
|
57587
57799
|
},
|
|
57588
57800
|
postAnnouncement: async (payload) => {
|
|
57801
|
+
if (hubReleaseCiRun(process.env)) throw new Error(HUB_RELEASE_CI_ANNOUNCE_DEFERRED);
|
|
57589
57802
|
const res = await releaseAnnouncement(payload, registryClientDeps(await loadConfig()));
|
|
57590
57803
|
const body = res.body;
|
|
57591
57804
|
if (!res.ok) throw new Error(body?.error ?? `Hub announcement failed (HTTP ${res.status}); verify delivery before retrying`);
|
|
@@ -57669,11 +57882,13 @@ function renderReleaseSupersedeDeploy(r) {
|
|
|
57669
57882
|
}
|
|
57670
57883
|
function renderReleaseSupersedePublish(r) {
|
|
57671
57884
|
const base = `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57672
|
-
|
|
57885
|
+
if (!r.gatewayDeploy) return base;
|
|
57886
|
+
return r.gatewayDeploy.state === "skipped" ? `${base}; the deferred Jerv Gateway operator-host deploy leg was SKIPPED because the failed publish never produced an installer payload (jerv-gateway=skipped, superseded): ${r.gatewayDeploy.note}` : `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the supersession (jerv-gateway=complete): ${r.gatewayDeploy.note}`;
|
|
57673
57887
|
}
|
|
57674
57888
|
function renderReleaseSupersedeDeployAndPublish(r) {
|
|
57675
57889
|
const base = `mmi-cli devops release --supersede-deploy --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57676
|
-
|
|
57890
|
+
if (!r.gatewayDeploy) return base;
|
|
57891
|
+
return r.gatewayDeploy.state === "skipped" ? `${base}; the deferred Jerv Gateway operator-host deploy leg was SKIPPED because the failed publish never produced an installer payload (jerv-gateway=skipped, superseded): ${r.gatewayDeploy.note}` : `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the closure (jerv-gateway=complete): ${r.gatewayDeploy.note}`;
|
|
57677
57892
|
}
|
|
57678
57893
|
function renderReleasePublishRetry(r) {
|
|
57679
57894
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
@@ -58095,7 +58310,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
58095
58310
|
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
58096
58311
|
}
|
|
58097
58312
|
try {
|
|
58098
|
-
const result = await runReleaseAbort(trainApplyDeps(), { approved: true });
|
|
58313
|
+
const result = await runReleaseAbort(trainApplyDeps(), { approved: true, ciCheckout: hubReleaseCiRun(process.env) !== null });
|
|
58099
58314
|
return emitTrainResult("release --abort", o.json ? JSON.stringify(result, null, 2) : renderReleaseAbort(result), o.out);
|
|
58100
58315
|
} catch (e) {
|
|
58101
58316
|
return failGraceful(`release --abort: ${e.message}`);
|
|
@@ -60559,29 +60774,23 @@ var surfaces_default = {
|
|
|
60559
60774
|
]
|
|
60560
60775
|
},
|
|
60561
60776
|
{
|
|
60562
|
-
token: "
|
|
60563
|
-
displayName: "
|
|
60777
|
+
token: "openbot",
|
|
60778
|
+
displayName: "OpenBot",
|
|
60564
60779
|
lifecycle: "active",
|
|
60565
60780
|
artifactIds: [
|
|
60566
|
-
"mmi-
|
|
60781
|
+
"mmi-openbot-plugin",
|
|
60567
60782
|
"mmi-skills",
|
|
60568
60783
|
"mmi-cli"
|
|
60569
60784
|
],
|
|
60570
60785
|
plannedArtifactIds: [],
|
|
60571
60786
|
assembly: {
|
|
60572
|
-
mode: "
|
|
60573
|
-
rootPath: "
|
|
60574
|
-
sync: [
|
|
60575
|
-
{
|
|
60576
|
-
mode: "directories",
|
|
60577
|
-
sourcePath: "skills",
|
|
60578
|
-
targetPath: "packages/hermes-plugin/skills"
|
|
60579
|
-
}
|
|
60580
|
-
]
|
|
60787
|
+
mode: "source-tree",
|
|
60788
|
+
rootPath: ".pi-plugin",
|
|
60789
|
+
sync: []
|
|
60581
60790
|
},
|
|
60582
60791
|
install: {
|
|
60583
60792
|
mechanism: "npm",
|
|
60584
|
-
locator: "@mutmutco/
|
|
60793
|
+
locator: "@mutmutco/pi-plugin (npm transport; MMI-Hub extracts its skills into each OpenBot agent workspace at <home>/OpenBot/Agents/agent-<uuid>/.agents/skills)"
|
|
60585
60794
|
},
|
|
60586
60795
|
upgrade: {
|
|
60587
60796
|
mechanism: "package-manager",
|
|
@@ -60590,10 +60799,10 @@ var surfaces_default = {
|
|
|
60590
60799
|
skills: {
|
|
60591
60800
|
delivery: "provisioned",
|
|
60592
60801
|
sourceSurfaceId: "mmi-skills",
|
|
60593
|
-
artifactId: "mmi-
|
|
60802
|
+
artifactId: "mmi-skills",
|
|
60594
60803
|
invocation: {
|
|
60595
|
-
entry: "
|
|
60596
|
-
any: "
|
|
60804
|
+
entry: "use the epic skill",
|
|
60805
|
+
any: "use the <skill> skill"
|
|
60597
60806
|
}
|
|
60598
60807
|
},
|
|
60599
60808
|
cli: {
|
|
@@ -60606,20 +60815,19 @@ var surfaces_default = {
|
|
|
60606
60815
|
repair: "mmi-hub"
|
|
60607
60816
|
},
|
|
60608
60817
|
certification: {
|
|
60609
|
-
|
|
60610
|
-
|
|
60611
|
-
|
|
60612
|
-
]
|
|
60818
|
+
hostProbe: "none",
|
|
60819
|
+
hostCommand: null,
|
|
60820
|
+
versionArgs: []
|
|
60613
60821
|
},
|
|
60614
60822
|
compatibility: {
|
|
60615
|
-
hostVersion: "0.
|
|
60616
|
-
certification: "
|
|
60823
|
+
hostVersion: "0.53.2",
|
|
60824
|
+
certification: "OpenBot desktop 0.53.2 ships no host CLI: surface presence is the agent workspace root (<home>/OpenBot/Agents/agent-*), never a --version probe"
|
|
60617
60825
|
},
|
|
60618
60826
|
enforcementCeilings: [
|
|
60619
|
-
"
|
|
60620
|
-
"
|
|
60621
|
-
"
|
|
60622
|
-
"Every
|
|
60827
|
+
"OpenBot ships no MMI hooks: this surface contributes the provisioned skills and a standalone mmi-cli only; enforcement is host-side.",
|
|
60828
|
+
"OpenBot's agent registry (%APPDATA%/OpenBot/openbot.db) is event-sourced and host-owned: MMI-Hub never reads or writes it, and enumerates agent workspaces by directory instead.",
|
|
60829
|
+
"OpenBot agent shells inherit the user PATH, so the standalone mmi-cli resolves; MMI provisions the skill payload, never the shell environment. OpenBot exports no identifying variable: mmi-cli recognizes an OpenBot session by a working directory inside <home>/OpenBot/Agents/agent-<uuid> (or an explicit MMI_AGENT_SURFACE=openbot), and a session working elsewhere is classified by its runtime.",
|
|
60830
|
+
"Every Agent workspace holds its own skills tree: MMI-Hub writes only <workspace>/.agents/skills/<skill> folders it recorded in .agents/skills/.mmi-managed.json, and never .claude/skills, an unrecorded folder, or a skill it does not own."
|
|
60623
60831
|
]
|
|
60624
60832
|
}
|
|
60625
60833
|
],
|
|
@@ -60983,41 +61191,36 @@ var surfaces_default = {
|
|
|
60983
61191
|
publishVisibility: "public"
|
|
60984
61192
|
},
|
|
60985
61193
|
{
|
|
60986
|
-
id: "mmi-
|
|
61194
|
+
id: "mmi-openbot-plugin",
|
|
60987
61195
|
classification: "packaging",
|
|
60988
61196
|
kind: "plugin",
|
|
60989
|
-
ownerPath: "
|
|
60990
|
-
deliveryPath: "
|
|
61197
|
+
ownerPath: ".pi-plugin/package.json",
|
|
61198
|
+
deliveryPath: ".pi-plugin/package.json",
|
|
60991
61199
|
delivery: "npm",
|
|
60992
|
-
applicability: "
|
|
61200
|
+
applicability: "OpenBot desktop agent workspaces; the SAME npm package as JervCode (@mutmutco/pi-plugin, surfaceToken jervcode) re-declared as OpenBot's transport so the registry can name a token-owned plugin artifact for a host that has no CLI of its own. No OpenBot-specific package exists or is planned.",
|
|
60993
61201
|
versionCoordinated: true,
|
|
60994
|
-
surfaceToken: "
|
|
61202
|
+
surfaceToken: "openbot",
|
|
60995
61203
|
versionPaths: [
|
|
60996
61204
|
{
|
|
60997
|
-
path: "
|
|
60998
|
-
pointer: "version"
|
|
60999
|
-
},
|
|
61000
|
-
{
|
|
61001
|
-
path: "packages/hermes-plugin/plugin.yaml",
|
|
61205
|
+
path: ".pi-plugin/package.json",
|
|
61002
61206
|
pointer: "version"
|
|
61003
61207
|
}
|
|
61004
61208
|
],
|
|
61005
61209
|
additionalPaths: [
|
|
61006
|
-
"
|
|
61007
|
-
"packages/hermes-plugin/__init__.py",
|
|
61008
|
-
"packages/hermes-plugin/skills"
|
|
61210
|
+
".pi-plugin/skills"
|
|
61009
61211
|
],
|
|
61010
61212
|
artifactIdentity: {
|
|
61011
61213
|
kind: "npm-pack",
|
|
61012
|
-
packagePath: "
|
|
61214
|
+
packagePath: ".pi-plugin"
|
|
61013
61215
|
},
|
|
61014
|
-
publishVisibility: "
|
|
61216
|
+
publishVisibility: "n/a"
|
|
61015
61217
|
}
|
|
61016
61218
|
]
|
|
61017
61219
|
};
|
|
61018
61220
|
|
|
61019
61221
|
// src/surface-doctor.ts
|
|
61020
|
-
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "
|
|
61222
|
+
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "openbot"]);
|
|
61223
|
+
var FRESH_PROCESS_SURFACES = /* @__PURE__ */ new Set(["openbot"]);
|
|
61021
61224
|
function isActiveToken(value) {
|
|
61022
61225
|
return TOKENS.has(value);
|
|
61023
61226
|
}
|
|
@@ -61068,7 +61271,7 @@ function diagnoseSurface(evidence) {
|
|
|
61068
61271
|
return { ...base, state: "clean" };
|
|
61069
61272
|
}
|
|
61070
61273
|
function reloadInstruction(descriptor) {
|
|
61071
|
-
if (descriptor.token === "
|
|
61274
|
+
if (descriptor.token === "openbot") return "start a fresh OpenBot agent run";
|
|
61072
61275
|
const verb = descriptor.reload === "workspace" ? "reload" : "restart";
|
|
61073
61276
|
return `${verb} ${descriptor.displayName}`;
|
|
61074
61277
|
}
|
|
@@ -61080,7 +61283,7 @@ function planSurfaceRepair(diagnosis) {
|
|
|
61080
61283
|
supported: false,
|
|
61081
61284
|
owner: "mmi-hub",
|
|
61082
61285
|
changes: [],
|
|
61083
|
-
// #5833: name the SURFACE this row is about —
|
|
61286
|
+
// #5833: name the SURFACE this row is about — a hard-coded host name sent every other
|
|
61084
61287
|
// mmi-hub-owned surface (Claude Code included) chasing a different host that was already current.
|
|
61085
61288
|
instruction: `run \`mmi-hub update\` to converge the MMI-managed ${descriptor.displayName} plugin, then ${reloadInstruction(descriptor)}`
|
|
61086
61289
|
};
|
|
@@ -61108,9 +61311,9 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
61108
61311
|
const detailByState = {
|
|
61109
61312
|
skipped: "skipped \u2014 host not active",
|
|
61110
61313
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61111
|
-
"pending-reload": `pending ${descriptor.token
|
|
61314
|
+
"pending-reload": `pending ${FRESH_PROCESS_SURFACES.has(descriptor.token) ? "fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
|
|
61112
61315
|
// #5833: the update already staged the released tree; the wording mirrors `mmi-hub status`.
|
|
61113
|
-
staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${descriptor.token
|
|
61316
|
+
staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${FRESH_PROCESS_SURFACES.has(descriptor.token) ? "start a fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} ${descriptor.displayName} to converge`,
|
|
61114
61317
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
61115
61318
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61116
61319
|
missing: "missing \u2014 no install record",
|
|
@@ -62656,8 +62859,17 @@ function buildRunWatchSpec(id, o) {
|
|
|
62656
62859
|
const timeoutMin = o.timeoutMin !== void 0 ? parseBrokerInt(o.timeoutMin, "--timeout", 1, 120) : 30;
|
|
62657
62860
|
return { program: "gh", args, timeoutMs: timeoutMin * 6e4 };
|
|
62658
62861
|
}
|
|
62862
|
+
function rejectPrViewQueueFields(fields, number) {
|
|
62863
|
+
if (!fields || !fields.split(",").includes("mergeQueueEntry")) return;
|
|
62864
|
+
throw new Error(
|
|
62865
|
+
`read pr view: 'mergeQueueEntry' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${number} --repo <owner/repo> --json\` and drop it from --json`
|
|
62866
|
+
);
|
|
62867
|
+
}
|
|
62659
62868
|
function buildPrViewSpec(ref, o) {
|
|
62660
|
-
const
|
|
62869
|
+
const number = parseBrokerId(ref, "PR number");
|
|
62870
|
+
const jsonFields = parseBrokerJsonFields(o.json);
|
|
62871
|
+
rejectPrViewQueueFields(jsonFields, number);
|
|
62872
|
+
const args = withJson(withRepo(["pr", "view", number], parseBrokerRepo(o.repo, "--repo")), jsonFields, o.json);
|
|
62661
62873
|
if (o.comments) args.push("--comments");
|
|
62662
62874
|
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
62663
62875
|
}
|
|
@@ -62730,21 +62942,50 @@ function ghExitCodeOf(error) {
|
|
|
62730
62942
|
const code = error.code;
|
|
62731
62943
|
return typeof code === "number" ? code : null;
|
|
62732
62944
|
}
|
|
62945
|
+
function brokerGhFailureHint(failureText, args) {
|
|
62946
|
+
const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
|
|
62947
|
+
const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
|
|
62948
|
+
if (unknownField === "mergeQueueEntry") {
|
|
62949
|
+
return `'mergeQueueEntry' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo <owner/repo> --json\` and drop it from --json; repeating this read cannot succeed`;
|
|
62950
|
+
}
|
|
62951
|
+
if (unknownField) {
|
|
62952
|
+
return `gh rejected --json field '${unknownField}' \u2014 drop it or use the typed read whose fields cover it (the "Available fields" list gh printed is its own vocabulary)`;
|
|
62953
|
+
}
|
|
62954
|
+
if (/Could not resolve to a PullRequest/i.test(failureText)) {
|
|
62955
|
+
return `GitHub could not resolve the PR${target ? ` #${target}` : ""} \u2014 verify the number and that --repo names the repo that owns it before retrying; an identical re-run fails the same way`;
|
|
62956
|
+
}
|
|
62957
|
+
return void 0;
|
|
62958
|
+
}
|
|
62733
62959
|
async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(chunk)) {
|
|
62734
62960
|
const env = brokerChildEnv();
|
|
62961
|
+
let output;
|
|
62735
62962
|
try {
|
|
62736
|
-
|
|
62737
|
-
return 0;
|
|
62963
|
+
output = redactPrivateCredential(await run(spec.args, { timeoutMs: spec.timeoutMs, env }), env);
|
|
62738
62964
|
} catch (e) {
|
|
62739
62965
|
const err = e;
|
|
62740
62966
|
if (typeof err.stdout === "string" && err.stdout) process.stderr.write(redactPrivateCredential(err.stdout, env));
|
|
62741
62967
|
const code = ghExitCodeOf(e);
|
|
62742
62968
|
if (code !== null) {
|
|
62743
62969
|
if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
|
|
62970
|
+
const failure = `${typeof err.stdout === "string" ? err.stdout : ""}${typeof err.stderr === "string" ? err.stderr : ""}`;
|
|
62971
|
+
const recovery = brokerGhFailureHint(failure, spec.args) ?? "inspect stderr; retry the same typed read and only parse stdout after success";
|
|
62972
|
+
process.stderr.write(`read: gh ${spec.args.slice(0, 2).join(" ")} failed on the App pool (exit ${code}) \u2014 ${recovery}
|
|
62973
|
+
`);
|
|
62744
62974
|
return code;
|
|
62745
62975
|
}
|
|
62746
62976
|
throw new Error(`read: gh ${spec.args.slice(0, 2).join(" ")} did not finish on the App pool (${err.message ?? e}) \u2014 re-run the same command to refresh (same custody); reads never fall back to personal`);
|
|
62747
62977
|
}
|
|
62978
|
+
if (spec.args.includes("--json")) {
|
|
62979
|
+
try {
|
|
62980
|
+
JSON.parse(output);
|
|
62981
|
+
} catch {
|
|
62982
|
+
process.stderr.write(`read: gh ${spec.args.slice(0, 2).join(" ")} returned ${output.trim() ? "invalid" : "empty"} JSON on the App pool \u2014 retry the same typed read; only parse stdout after success
|
|
62983
|
+
`);
|
|
62984
|
+
return 1;
|
|
62985
|
+
}
|
|
62986
|
+
}
|
|
62987
|
+
write(output);
|
|
62988
|
+
return 0;
|
|
62748
62989
|
}
|
|
62749
62990
|
async function runBrokerRestChecks(deps, spec, resolveRepo2) {
|
|
62750
62991
|
const repo = await resolveRepo2(spec.repo);
|
|
@@ -63032,11 +63273,33 @@ var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$
|
|
|
63032
63273
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
63033
63274
|
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
63034
63275
|
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
|
|
63276
|
+
var TYPED_READ_ROUTE_RE = new RegExp(`^repos/(${NAME})/(${NAME})/(issues|pulls)(?:/(comments/\\d+|\\d+)(?:/([a-z_]+))?)?$`);
|
|
63277
|
+
function typedReadHint(path2) {
|
|
63278
|
+
const match = TYPED_READ_ROUTE_RE.exec(path2.split("?")[0]);
|
|
63279
|
+
if (!match) return "";
|
|
63280
|
+
const [, owner, repo, kind, item, sub] = match;
|
|
63281
|
+
const slug = `${owner}/${repo}`;
|
|
63282
|
+
const say = (command, note = "") => ` Typed read for this route: \`${command}\`${note}.`;
|
|
63283
|
+
if (kind === "issues") {
|
|
63284
|
+
if (!item) return say(`mmi-cli devops read issue list --repo ${slug} --json number,title,state,labels`);
|
|
63285
|
+
if (item.startsWith("comments/")) {
|
|
63286
|
+
const id = item.slice("comments/".length);
|
|
63287
|
+
return say(`mmi-cli devops read issue view <issue> --repo ${slug} --json comments`, `; a comment is read through its issue, so match the comment whose url ends in issuecomment-${id}`);
|
|
63288
|
+
}
|
|
63289
|
+
if (sub === "events" || sub === "timeline") {
|
|
63290
|
+
return say(`mmi-cli devops read issue view ${item} --repo ${slug} --json state,stateReason,closedAt,labels,assignees,comments`, "; the raw event/timeline history itself has no read");
|
|
63291
|
+
}
|
|
63292
|
+
return say(`mmi-cli devops read issue view ${item} --repo ${slug} --json title,body,state,labels,comments`);
|
|
63293
|
+
}
|
|
63294
|
+
if (!item) return say(`mmi-cli devops read pr list --repo ${slug} --json number,title,state,headRefName`);
|
|
63295
|
+
if (item.startsWith("comments/")) return say(`mmi-cli devops read pr view <pr> --repo ${slug} --json comments,reviews`);
|
|
63296
|
+
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup`);
|
|
63297
|
+
}
|
|
63035
63298
|
function parseApiGetPath(rawPath) {
|
|
63036
63299
|
const path2 = rawPath.trim();
|
|
63037
63300
|
const refuse = () => {
|
|
63038
63301
|
const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
63039
|
-
return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${hint}`);
|
|
63302
|
+
return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${typedReadHint(path2)}${hint}`);
|
|
63040
63303
|
};
|
|
63041
63304
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
63042
63305
|
const q = path2.indexOf("?");
|
|
@@ -65737,22 +66000,23 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65737
66000
|
if (!token || token === "opencode") return void 0;
|
|
65738
66001
|
const descriptor = doctorSurface(token);
|
|
65739
66002
|
const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
|
|
65740
|
-
const
|
|
65741
|
-
const installedVersion =
|
|
66003
|
+
const native = token === "openbot" ? readOpenBotPluginEvidence(process.env) : void 0;
|
|
66004
|
+
const installedVersion = native?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
|
|
65742
66005
|
const stagedVersion = token === "claude" ? stagedClaudePluginVersion() : void 0;
|
|
65743
66006
|
surfaceEvidence = {
|
|
65744
66007
|
descriptor,
|
|
65745
|
-
//
|
|
65746
|
-
// absent root is skipped. Other mature surfaces retain their established org/install
|
|
65747
|
-
|
|
65748
|
-
|
|
65749
|
-
|
|
65750
|
-
|
|
65751
|
-
|
|
66008
|
+
// A native evidence reader IS the host evidence: a configured but uninstalled MMI tree is missing,
|
|
66009
|
+
// while an absent root is skipped. Other mature surfaces retain their established org/install
|
|
66010
|
+
// applicability.
|
|
66011
|
+
applicable: native ? native.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
|
|
66012
|
+
installRecordPresent: native?.installRecordPresent ?? snapshot.installRecordPresent,
|
|
66013
|
+
deliveryPresent: native ? native.installRecordPresent : snapshot.marketplaceClonePresent,
|
|
66014
|
+
payloadPresent: native?.payloadPresent ?? snapshot.pluginCachePresent,
|
|
66015
|
+
manifest: native?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
|
|
65752
66016
|
guardState: buildPluginGuardDecision(snapshot).state,
|
|
65753
66017
|
...installedVersion ? { installedVersion } : {},
|
|
65754
66018
|
...stagedVersion ? { stagedVersion } : {},
|
|
65755
|
-
...
|
|
66019
|
+
...native?.receipt ? { receipt: native.receipt } : {}
|
|
65756
66020
|
};
|
|
65757
66021
|
return surfaceEvidence;
|
|
65758
66022
|
};
|
package/dist/runbook-sweep.cjs
CHANGED
|
@@ -8640,6 +8640,7 @@ init_error_codes();
|
|
|
8640
8640
|
|
|
8641
8641
|
// src/secrets-transport.ts
|
|
8642
8642
|
init_client_version();
|
|
8643
|
+
init_hub_auth();
|
|
8643
8644
|
var KEY_MAX_PATH_SEGMENTS = 4;
|
|
8644
8645
|
var KEY_RE = new RegExp(`^(?:[a-z0-9][a-z0-9-]*/){0,${KEY_MAX_PATH_SEGMENTS}}[A-Za-z][A-Za-z0-9_]*$`);
|
|
8645
8646
|
|
package/package.json
CHANGED