@mutmutco/codex-plugin 4.5.227 → 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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +429 -201
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +2 -2
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/seeds/suite-gate.template.yml +1 -1
- package/skills/hotfix/SKILL.md +1 -1
- package/skills/mmi-help/SKILL.md +1 -1
- package/skills/org-guide/references/getting-started.md +4 -5
- package/skills/org-guide/references/work-and-releases.md +7 -1
- package/skills/rcand/SKILL.md +1 -1
- package/skills/release/SKILL.md +3 -1
- package/skills/secrets/SKILL.md +1 -1
package/cli/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) {
|
|
@@ -18738,6 +18837,7 @@ function shouldWithdrawSurfaceFlag(result) {
|
|
|
18738
18837
|
|
|
18739
18838
|
// src/secrets-transport.ts
|
|
18740
18839
|
init_client_version();
|
|
18840
|
+
init_hub_auth();
|
|
18741
18841
|
var OWNER = "mutmutco";
|
|
18742
18842
|
var SSM_ROOT = "/mmi-future";
|
|
18743
18843
|
var ORG_INFRA_SLUG = "_org";
|
|
@@ -18899,6 +18999,17 @@ function errorDetail(body) {
|
|
|
18899
18999
|
const error = typeof body.error === "string" ? body.error : "";
|
|
18900
19000
|
return error ? `: ${error}` : "";
|
|
18901
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
|
+
}
|
|
18902
19013
|
async function upgradeMessage(res, body) {
|
|
18903
19014
|
if (res.status !== 426) return null;
|
|
18904
19015
|
return upgradeRequiredError(res, body ?? await readJsonBody(res));
|
|
@@ -18947,7 +19058,7 @@ async function secretsList(deps, opts) {
|
|
|
18947
19058
|
return;
|
|
18948
19059
|
}
|
|
18949
19060
|
if (!res.ok) {
|
|
18950
|
-
deps.err(await upgradeMessage(res) ??
|
|
19061
|
+
deps.err(await upgradeMessage(res) ?? refusedLine("secrets list failed", res, await readJsonBody(res)));
|
|
18951
19062
|
return;
|
|
18952
19063
|
}
|
|
18953
19064
|
const { secrets } = await res.json();
|
|
@@ -19332,6 +19443,10 @@ function missingRequiredKeys(stage, required, present, declaredSecrets) {
|
|
|
19332
19443
|
}
|
|
19333
19444
|
async function secretsPreflight(deps, opts) {
|
|
19334
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
|
+
}
|
|
19335
19450
|
const qs = new URLSearchParams({ repo }).toString();
|
|
19336
19451
|
let res;
|
|
19337
19452
|
try {
|
|
@@ -20268,7 +20383,7 @@ async function secretsRequest(deps, key, opts) {
|
|
|
20268
20383
|
});
|
|
20269
20384
|
const body = await readJsonBody(res);
|
|
20270
20385
|
if (!res.ok) {
|
|
20271
|
-
deps.err(await upgradeMessage(res, body) ??
|
|
20386
|
+
deps.err(await upgradeMessage(res, body) ?? refusedLine("secrets request failed", res, body));
|
|
20272
20387
|
return false;
|
|
20273
20388
|
}
|
|
20274
20389
|
if (opts.json) {
|
|
@@ -20965,7 +21080,7 @@ async function fetchSecretForUse(deps, { repo, key, slug }) {
|
|
|
20965
21080
|
return null;
|
|
20966
21081
|
}
|
|
20967
21082
|
deps.err(
|
|
20968
|
-
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))
|
|
20969
21084
|
);
|
|
20970
21085
|
return null;
|
|
20971
21086
|
}
|
|
@@ -24973,6 +25088,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
24973
25088
|
// src/gate-budget.ts
|
|
24974
25089
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
24975
25090
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25091
|
+
var BLESSED_KIT_AGENT_FILES_SHA = "a8b273ed418e703c0aa501a53ea87228ba28b1f0";
|
|
24976
25092
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
24977
25093
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
24978
25094
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25348,6 +25464,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25348
25464
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
25349
25465
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
25350
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,
|
|
25351
25470
|
// #8129: the guard and its bundled CLI must come from the same immutable Hub revision.
|
|
25352
25471
|
GATE_RUNBOOK_SHA: BLESSED_RUNBOOK_GUARD_SHA,
|
|
25353
25472
|
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
@@ -27091,10 +27210,10 @@ var rollout_plan_default = {
|
|
|
27091
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)."
|
|
27092
27211
|
},
|
|
27093
27212
|
baseline: {
|
|
27094
|
-
version: "4.5.
|
|
27095
|
-
tag: "v4.5.
|
|
27096
|
-
commit: "
|
|
27097
|
-
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"
|
|
27098
27217
|
},
|
|
27099
27218
|
exitCriterion: "fleet-n-of-n",
|
|
27100
27219
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27111,14 +27230,14 @@ var rollout_plan_default = {
|
|
|
27111
27230
|
repo: "mutmutco/mmi-hub",
|
|
27112
27231
|
role: "canary",
|
|
27113
27232
|
schedule: "train",
|
|
27114
|
-
v3Target: "v4.5.
|
|
27233
|
+
v3Target: "v4.5.228"
|
|
27115
27234
|
}
|
|
27116
27235
|
],
|
|
27117
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.",
|
|
27118
27237
|
rollback: {
|
|
27119
27238
|
independent: true,
|
|
27120
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
27121
|
-
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)"
|
|
27122
27241
|
}
|
|
27123
27242
|
},
|
|
27124
27243
|
{
|
|
@@ -29275,7 +29394,8 @@ async function buildTrainApplyContext(deps) {
|
|
|
29275
29394
|
repo,
|
|
29276
29395
|
owner,
|
|
29277
29396
|
slug: name.toLowerCase(),
|
|
29278
|
-
login
|
|
29397
|
+
login,
|
|
29398
|
+
...verdict.authenticatedDelegation === true ? { delegatedActor: login } : {}
|
|
29279
29399
|
};
|
|
29280
29400
|
}
|
|
29281
29401
|
function commandAuthorityLabel(owner) {
|
|
@@ -32052,57 +32172,24 @@ function installedClaudePluginVersion() {
|
|
|
32052
32172
|
return void 0;
|
|
32053
32173
|
}
|
|
32054
32174
|
}
|
|
32055
|
-
function
|
|
32056
|
-
const
|
|
32057
|
-
const
|
|
32058
|
-
const
|
|
32059
|
-
const manifestPath = (0, import_node_path22.join)(root, "plugin.yaml");
|
|
32060
|
-
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;
|
|
32061
32179
|
let manifest = "missing";
|
|
32062
|
-
|
|
32063
|
-
const text = (0, import_node_fs25.readFileSync)(manifestPath, "utf8");
|
|
32064
|
-
let version;
|
|
32065
|
-
try {
|
|
32066
|
-
const parsed = JSON.parse(text).version;
|
|
32067
|
-
if (typeof parsed === "string" && parsed.trim()) version = parsed.trim();
|
|
32068
|
-
} catch {
|
|
32069
|
-
}
|
|
32070
|
-
if (!version) {
|
|
32071
|
-
const match = /^version:\s*["']?([^\s"'#]+)["']?\s*(?:#.*)?$/m.exec(text);
|
|
32072
|
-
version = match?.[1];
|
|
32073
|
-
}
|
|
32074
|
-
if (version) {
|
|
32075
|
-
installedVersion = version;
|
|
32076
|
-
manifest = "valid";
|
|
32077
|
-
} else if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32078
|
-
} catch {
|
|
32079
|
-
if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32080
|
-
}
|
|
32081
|
-
let skills = false;
|
|
32082
|
-
try {
|
|
32083
|
-
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();
|
|
32084
|
-
} catch {
|
|
32085
|
-
}
|
|
32086
|
-
let provisioned = false;
|
|
32087
|
-
const provisionedRoot = (0, import_node_path22.join)(host, "skills", "mmi");
|
|
32088
|
-
try {
|
|
32089
|
-
provisioned = (0, import_node_fs25.existsSync)(provisionedRoot) && (0, import_node_fs25.statSync)(provisionedRoot).isDirectory();
|
|
32090
|
-
} catch {
|
|
32091
|
-
}
|
|
32092
|
-
const treeOk = (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, "__init__.py")) && skills && manifest === "valid";
|
|
32093
|
-
const receipt = treeOk && !provisioned ? `hermes-skills-unprovisioned:${provisionedRoot}` : void 0;
|
|
32180
|
+
if (recordPath) manifest = record2?.version ? "valid" : "invalid";
|
|
32094
32181
|
return {
|
|
32095
|
-
hostPresent:
|
|
32096
|
-
installRecordPresent,
|
|
32182
|
+
hostPresent: workspaces.length > 0,
|
|
32183
|
+
installRecordPresent: record2 !== null,
|
|
32097
32184
|
manifest,
|
|
32098
|
-
payloadPresent:
|
|
32099
|
-
...
|
|
32100
|
-
...
|
|
32185
|
+
payloadPresent: record2 !== null && openbotWorkspaceSkillsHealthy(env),
|
|
32186
|
+
...record2?.version ? { installedVersion: record2.version } : {},
|
|
32187
|
+
...recordPath && !record2?.version ? { receipt: `openbot-ownership-unreadable:${recordPath}` } : {}
|
|
32101
32188
|
};
|
|
32102
32189
|
}
|
|
32103
32190
|
function installedSurfacePluginVersion(surface) {
|
|
32104
32191
|
const token = surfaceToken(surface);
|
|
32105
|
-
if (token === "
|
|
32192
|
+
if (token === "openbot") return readOpenBotPluginEvidence().installedVersion;
|
|
32106
32193
|
if (token === "jervcode") {
|
|
32107
32194
|
return installedJervCodePackageVersion();
|
|
32108
32195
|
}
|
|
@@ -34301,6 +34388,22 @@ function supersedeablePublishLegShape(rec) {
|
|
|
34301
34388
|
function deferredGatewayDeployLegShape(rec) {
|
|
34302
34389
|
return rec.state === "pending" && rec.runId == null && rec.workflow === "jerv-gateway";
|
|
34303
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
|
+
}
|
|
34304
34407
|
async function proveOriginPromotionOrArchived(deps, prior, path2, label, ref, fence) {
|
|
34305
34408
|
let liveSha = "";
|
|
34306
34409
|
try {
|
|
@@ -34386,23 +34489,29 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
|
34386
34489
|
};
|
|
34387
34490
|
}
|
|
34388
34491
|
if (deferredGateway) {
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
prior.
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
|
|
34398
|
-
|
|
34399
|
-
|
|
34400
|
-
if (dispatch.deployStatus !== "success") {
|
|
34401
|
-
throw new Error(
|
|
34402
|
-
`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" }
|
|
34403
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 };
|
|
34404
34514
|
}
|
|
34405
|
-
gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
|
|
34406
34515
|
}
|
|
34407
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)`;
|
|
34408
34517
|
await fence(() => atomicWriteJson(path2, {
|
|
@@ -34486,7 +34595,11 @@ async function supersedeContentCausedDeployAndPublishLegs(deps, cwd) {
|
|
|
34486
34595
|
}
|
|
34487
34596
|
let deployEntry;
|
|
34488
34597
|
let gatewayDeploy;
|
|
34489
|
-
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) {
|
|
34490
34603
|
const dispatch = await appendJervGatewayReleaseDeploy(
|
|
34491
34604
|
deps,
|
|
34492
34605
|
prior.repo,
|
|
@@ -37404,7 +37517,15 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37404
37517
|
);
|
|
37405
37518
|
}
|
|
37406
37519
|
await requireCleanTree(deps);
|
|
37407
|
-
|
|
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") {
|
|
37408
37529
|
throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
|
|
37409
37530
|
}
|
|
37410
37531
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
@@ -37414,7 +37535,7 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37414
37535
|
const tagSha = await probeRemoteTag(deps, tag);
|
|
37415
37536
|
if (!tagSha) throw new Error(`release --abort: ${tag} is not on origin; nothing was written`);
|
|
37416
37537
|
const localTagSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
37417
|
-
const localMainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37538
|
+
const localMainSha = ciCheckout ? tagSha : clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37418
37539
|
const originMainSha = clean2(await deps.run("git", ["rev-parse", "origin/main"]));
|
|
37419
37540
|
if (localTagSha !== tagSha || localMainSha !== tagSha) {
|
|
37420
37541
|
throw new Error(
|
|
@@ -37445,6 +37566,17 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37445
37566
|
throw new Error(`release --abort: origin still reports ${tag} after deletion; local state was preserved`);
|
|
37446
37567
|
}
|
|
37447
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
|
+
}
|
|
37448
37580
|
await deps.run("git", ["checkout", "development"]);
|
|
37449
37581
|
await ffOnlyPull(deps, "development");
|
|
37450
37582
|
await deps.run("git", ["branch", "-f", "main", "origin/main"]);
|
|
@@ -40786,7 +40918,7 @@ function registerBoardCommands(program3) {
|
|
|
40786
40918
|
return `Claimed ${ref} for ${holder} - In Progress${reclaimed}`;
|
|
40787
40919
|
}
|
|
40788
40920
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
40789
|
-
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));
|
|
40790
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) => {
|
|
40791
40923
|
try {
|
|
40792
40924
|
if (o.since !== void 0 && !/^\d{1,15}$/.test(o.since)) throw new Error("--since must be a non-negative integer cursor");
|
|
@@ -43006,7 +43138,7 @@ function registerSecretsCommands(program3) {
|
|
|
43006
43138
|
}
|
|
43007
43139
|
const { keys, command, ...o } = parsed.value;
|
|
43008
43140
|
const code = secretsUseExitCode(await secretsUse(d, keys, { ...o, command }));
|
|
43009
|
-
if (code !== void 0) process.exitCode = code;
|
|
43141
|
+
if (code !== void 0) process.exitCode = lastHubAuthFailure()?.exitCode ?? code;
|
|
43010
43142
|
});
|
|
43011
43143
|
});
|
|
43012
43144
|
withDeclaredOptions(
|
|
@@ -51246,6 +51378,61 @@ function prMergeQueueLine(result) {
|
|
|
51246
51378
|
// src/command-register-train-operations.ts
|
|
51247
51379
|
init_github_client();
|
|
51248
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
|
+
|
|
51249
51436
|
// src/project-info-sync.ts
|
|
51250
51437
|
var import_node_fs38 = require("node:fs");
|
|
51251
51438
|
var import_node_path36 = require("node:path");
|
|
@@ -56862,21 +57049,21 @@ ${list}`);
|
|
|
56862
57049
|
// #8456: a pinned land has no update lane at all.
|
|
56863
57050
|
...o.expectHead ? {} : { updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)) },
|
|
56864
57051
|
pollMerged: async (prNumber, repo, deadlineMs) => {
|
|
56865
|
-
let
|
|
57052
|
+
let lastFailure2;
|
|
56866
57053
|
while (Date.now() < deadlineMs) {
|
|
56867
57054
|
const read = await pollRestPrMerged(prNumber, repo);
|
|
56868
57055
|
if (read.state === "merged") return true;
|
|
56869
57056
|
if (read.state === "failed") {
|
|
56870
|
-
|
|
57057
|
+
lastFailure2 = read.error;
|
|
56871
57058
|
console.warn(`pr land: merged-state read FAILED (${read.error}) \u2014 retrying; this is not evidence the PR is unmerged`);
|
|
56872
57059
|
} else {
|
|
56873
|
-
|
|
57060
|
+
lastFailure2 = void 0;
|
|
56874
57061
|
}
|
|
56875
57062
|
await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
|
|
56876
57063
|
}
|
|
56877
|
-
if (
|
|
57064
|
+
if (lastFailure2) {
|
|
56878
57065
|
throw new Error(
|
|
56879
|
-
`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.`
|
|
56880
57067
|
);
|
|
56881
57068
|
}
|
|
56882
57069
|
return false;
|
|
@@ -57539,6 +57726,8 @@ function trainApplyDeps() {
|
|
|
57539
57726
|
// parses, so the ONE self-spawn seam derives the prefix through the same rule the refusal names.
|
|
57540
57727
|
runSelf: async (args) => (await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
|
|
57541
57728
|
trainAuthority: async (repo) => {
|
|
57729
|
+
const ci2 = await hubReleaseCiAuthority(repo, { env: process.env, getJson: pinnedGitHubGetJson(process.env.GH_TOKEN) });
|
|
57730
|
+
if (ci2) return ci2;
|
|
57542
57731
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
57543
57732
|
return verdict.ok ? {
|
|
57544
57733
|
ok: true,
|
|
@@ -57609,6 +57798,7 @@ function trainApplyDeps() {
|
|
|
57609
57798
|
return (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS, ...env ? { env } : {} })).stdout;
|
|
57610
57799
|
},
|
|
57611
57800
|
postAnnouncement: async (payload) => {
|
|
57801
|
+
if (hubReleaseCiRun(process.env)) throw new Error(HUB_RELEASE_CI_ANNOUNCE_DEFERRED);
|
|
57612
57802
|
const res = await releaseAnnouncement(payload, registryClientDeps(await loadConfig()));
|
|
57613
57803
|
const body = res.body;
|
|
57614
57804
|
if (!res.ok) throw new Error(body?.error ?? `Hub announcement failed (HTTP ${res.status}); verify delivery before retrying`);
|
|
@@ -57692,11 +57882,13 @@ function renderReleaseSupersedeDeploy(r) {
|
|
|
57692
57882
|
}
|
|
57693
57883
|
function renderReleaseSupersedePublish(r) {
|
|
57694
57884
|
const base = `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57695
|
-
|
|
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}`;
|
|
57696
57887
|
}
|
|
57697
57888
|
function renderReleaseSupersedeDeployAndPublish(r) {
|
|
57698
57889
|
const base = `mmi-cli devops release --supersede-deploy --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57699
|
-
|
|
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}`;
|
|
57700
57892
|
}
|
|
57701
57893
|
function renderReleasePublishRetry(r) {
|
|
57702
57894
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
@@ -58118,7 +58310,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
58118
58310
|
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
58119
58311
|
}
|
|
58120
58312
|
try {
|
|
58121
|
-
const result = await runReleaseAbort(trainApplyDeps(), { approved: true });
|
|
58313
|
+
const result = await runReleaseAbort(trainApplyDeps(), { approved: true, ciCheckout: hubReleaseCiRun(process.env) !== null });
|
|
58122
58314
|
return emitTrainResult("release --abort", o.json ? JSON.stringify(result, null, 2) : renderReleaseAbort(result), o.out);
|
|
58123
58315
|
} catch (e) {
|
|
58124
58316
|
return failGraceful(`release --abort: ${e.message}`);
|
|
@@ -60582,29 +60774,23 @@ var surfaces_default = {
|
|
|
60582
60774
|
]
|
|
60583
60775
|
},
|
|
60584
60776
|
{
|
|
60585
|
-
token: "
|
|
60586
|
-
displayName: "
|
|
60777
|
+
token: "openbot",
|
|
60778
|
+
displayName: "OpenBot",
|
|
60587
60779
|
lifecycle: "active",
|
|
60588
60780
|
artifactIds: [
|
|
60589
|
-
"mmi-
|
|
60781
|
+
"mmi-openbot-plugin",
|
|
60590
60782
|
"mmi-skills",
|
|
60591
60783
|
"mmi-cli"
|
|
60592
60784
|
],
|
|
60593
60785
|
plannedArtifactIds: [],
|
|
60594
60786
|
assembly: {
|
|
60595
|
-
mode: "
|
|
60596
|
-
rootPath: "
|
|
60597
|
-
sync: [
|
|
60598
|
-
{
|
|
60599
|
-
mode: "directories",
|
|
60600
|
-
sourcePath: "skills",
|
|
60601
|
-
targetPath: "packages/hermes-plugin/skills"
|
|
60602
|
-
}
|
|
60603
|
-
]
|
|
60787
|
+
mode: "source-tree",
|
|
60788
|
+
rootPath: ".pi-plugin",
|
|
60789
|
+
sync: []
|
|
60604
60790
|
},
|
|
60605
60791
|
install: {
|
|
60606
60792
|
mechanism: "npm",
|
|
60607
|
-
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)"
|
|
60608
60794
|
},
|
|
60609
60795
|
upgrade: {
|
|
60610
60796
|
mechanism: "package-manager",
|
|
@@ -60613,10 +60799,10 @@ var surfaces_default = {
|
|
|
60613
60799
|
skills: {
|
|
60614
60800
|
delivery: "provisioned",
|
|
60615
60801
|
sourceSurfaceId: "mmi-skills",
|
|
60616
|
-
artifactId: "mmi-
|
|
60802
|
+
artifactId: "mmi-skills",
|
|
60617
60803
|
invocation: {
|
|
60618
|
-
entry: "
|
|
60619
|
-
any: "
|
|
60804
|
+
entry: "use the epic skill",
|
|
60805
|
+
any: "use the <skill> skill"
|
|
60620
60806
|
}
|
|
60621
60807
|
},
|
|
60622
60808
|
cli: {
|
|
@@ -60629,20 +60815,19 @@ var surfaces_default = {
|
|
|
60629
60815
|
repair: "mmi-hub"
|
|
60630
60816
|
},
|
|
60631
60817
|
certification: {
|
|
60632
|
-
|
|
60633
|
-
|
|
60634
|
-
|
|
60635
|
-
]
|
|
60818
|
+
hostProbe: "none",
|
|
60819
|
+
hostCommand: null,
|
|
60820
|
+
versionArgs: []
|
|
60636
60821
|
},
|
|
60637
60822
|
compatibility: {
|
|
60638
|
-
hostVersion: "0.
|
|
60639
|
-
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"
|
|
60640
60825
|
},
|
|
60641
60826
|
enforcementCeilings: [
|
|
60642
|
-
"
|
|
60643
|
-
"
|
|
60644
|
-
"
|
|
60645
|
-
"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."
|
|
60646
60831
|
]
|
|
60647
60832
|
}
|
|
60648
60833
|
],
|
|
@@ -61006,41 +61191,36 @@ var surfaces_default = {
|
|
|
61006
61191
|
publishVisibility: "public"
|
|
61007
61192
|
},
|
|
61008
61193
|
{
|
|
61009
|
-
id: "mmi-
|
|
61194
|
+
id: "mmi-openbot-plugin",
|
|
61010
61195
|
classification: "packaging",
|
|
61011
61196
|
kind: "plugin",
|
|
61012
|
-
ownerPath: "
|
|
61013
|
-
deliveryPath: "
|
|
61197
|
+
ownerPath: ".pi-plugin/package.json",
|
|
61198
|
+
deliveryPath: ".pi-plugin/package.json",
|
|
61014
61199
|
delivery: "npm",
|
|
61015
|
-
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.",
|
|
61016
61201
|
versionCoordinated: true,
|
|
61017
|
-
surfaceToken: "
|
|
61202
|
+
surfaceToken: "openbot",
|
|
61018
61203
|
versionPaths: [
|
|
61019
61204
|
{
|
|
61020
|
-
path: "
|
|
61021
|
-
pointer: "version"
|
|
61022
|
-
},
|
|
61023
|
-
{
|
|
61024
|
-
path: "packages/hermes-plugin/plugin.yaml",
|
|
61205
|
+
path: ".pi-plugin/package.json",
|
|
61025
61206
|
pointer: "version"
|
|
61026
61207
|
}
|
|
61027
61208
|
],
|
|
61028
61209
|
additionalPaths: [
|
|
61029
|
-
"
|
|
61030
|
-
"packages/hermes-plugin/__init__.py",
|
|
61031
|
-
"packages/hermes-plugin/skills"
|
|
61210
|
+
".pi-plugin/skills"
|
|
61032
61211
|
],
|
|
61033
61212
|
artifactIdentity: {
|
|
61034
61213
|
kind: "npm-pack",
|
|
61035
|
-
packagePath: "
|
|
61214
|
+
packagePath: ".pi-plugin"
|
|
61036
61215
|
},
|
|
61037
|
-
publishVisibility: "
|
|
61216
|
+
publishVisibility: "n/a"
|
|
61038
61217
|
}
|
|
61039
61218
|
]
|
|
61040
61219
|
};
|
|
61041
61220
|
|
|
61042
61221
|
// src/surface-doctor.ts
|
|
61043
|
-
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"]);
|
|
61044
61224
|
function isActiveToken(value) {
|
|
61045
61225
|
return TOKENS.has(value);
|
|
61046
61226
|
}
|
|
@@ -61091,7 +61271,7 @@ function diagnoseSurface(evidence) {
|
|
|
61091
61271
|
return { ...base, state: "clean" };
|
|
61092
61272
|
}
|
|
61093
61273
|
function reloadInstruction(descriptor) {
|
|
61094
|
-
if (descriptor.token === "
|
|
61274
|
+
if (descriptor.token === "openbot") return "start a fresh OpenBot agent run";
|
|
61095
61275
|
const verb = descriptor.reload === "workspace" ? "reload" : "restart";
|
|
61096
61276
|
return `${verb} ${descriptor.displayName}`;
|
|
61097
61277
|
}
|
|
@@ -61103,7 +61283,7 @@ function planSurfaceRepair(diagnosis) {
|
|
|
61103
61283
|
supported: false,
|
|
61104
61284
|
owner: "mmi-hub",
|
|
61105
61285
|
changes: [],
|
|
61106
|
-
// #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
|
|
61107
61287
|
// mmi-hub-owned surface (Claude Code included) chasing a different host that was already current.
|
|
61108
61288
|
instruction: `run \`mmi-hub update\` to converge the MMI-managed ${descriptor.displayName} plugin, then ${reloadInstruction(descriptor)}`
|
|
61109
61289
|
};
|
|
@@ -61131,9 +61311,9 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
61131
61311
|
const detailByState = {
|
|
61132
61312
|
skipped: "skipped \u2014 host not active",
|
|
61133
61313
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61134
|
-
"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})` : ""}`,
|
|
61135
61315
|
// #5833: the update already staged the released tree; the wording mirrors `mmi-hub status`.
|
|
61136
|
-
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`,
|
|
61137
61317
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
61138
61318
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61139
61319
|
missing: "missing \u2014 no install record",
|
|
@@ -62679,8 +62859,17 @@ function buildRunWatchSpec(id, o) {
|
|
|
62679
62859
|
const timeoutMin = o.timeoutMin !== void 0 ? parseBrokerInt(o.timeoutMin, "--timeout", 1, 120) : 30;
|
|
62680
62860
|
return { program: "gh", args, timeoutMs: timeoutMin * 6e4 };
|
|
62681
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
|
+
}
|
|
62682
62868
|
function buildPrViewSpec(ref, o) {
|
|
62683
|
-
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);
|
|
62684
62873
|
if (o.comments) args.push("--comments");
|
|
62685
62874
|
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
62686
62875
|
}
|
|
@@ -62753,6 +62942,20 @@ function ghExitCodeOf(error) {
|
|
|
62753
62942
|
const code = error.code;
|
|
62754
62943
|
return typeof code === "number" ? code : null;
|
|
62755
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
|
+
}
|
|
62756
62959
|
async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(chunk)) {
|
|
62757
62960
|
const env = brokerChildEnv();
|
|
62758
62961
|
let output;
|
|
@@ -62764,7 +62967,9 @@ async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(ch
|
|
|
62764
62967
|
const code = ghExitCodeOf(e);
|
|
62765
62968
|
if (code !== null) {
|
|
62766
62969
|
if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
|
|
62767
|
-
|
|
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}
|
|
62768
62973
|
`);
|
|
62769
62974
|
return code;
|
|
62770
62975
|
}
|
|
@@ -63068,11 +63273,33 @@ var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$
|
|
|
63068
63273
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
63069
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>)";
|
|
63070
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
|
+
}
|
|
63071
63298
|
function parseApiGetPath(rawPath) {
|
|
63072
63299
|
const path2 = rawPath.trim();
|
|
63073
63300
|
const refuse = () => {
|
|
63074
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." : "";
|
|
63075
|
-
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}`);
|
|
63076
63303
|
};
|
|
63077
63304
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
63078
63305
|
const q = path2.indexOf("?");
|
|
@@ -65773,22 +66000,23 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65773
66000
|
if (!token || token === "opencode") return void 0;
|
|
65774
66001
|
const descriptor = doctorSurface(token);
|
|
65775
66002
|
const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
|
|
65776
|
-
const
|
|
65777
|
-
const installedVersion =
|
|
66003
|
+
const native = token === "openbot" ? readOpenBotPluginEvidence(process.env) : void 0;
|
|
66004
|
+
const installedVersion = native?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
|
|
65778
66005
|
const stagedVersion = token === "claude" ? stagedClaudePluginVersion() : void 0;
|
|
65779
66006
|
surfaceEvidence = {
|
|
65780
66007
|
descriptor,
|
|
65781
|
-
//
|
|
65782
|
-
// absent root is skipped. Other mature surfaces retain their established org/install
|
|
65783
|
-
|
|
65784
|
-
|
|
65785
|
-
|
|
65786
|
-
|
|
65787
|
-
|
|
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"),
|
|
65788
66016
|
guardState: buildPluginGuardDecision(snapshot).state,
|
|
65789
66017
|
...installedVersion ? { installedVersion } : {},
|
|
65790
66018
|
...stagedVersion ? { stagedVersion } : {},
|
|
65791
|
-
...
|
|
66019
|
+
...native?.receipt ? { receipt: native.receipt } : {}
|
|
65792
66020
|
};
|
|
65793
66021
|
return surfaceEvidence;
|
|
65794
66022
|
};
|